diff --git a/.gitignore b/.gitignore index a55744f251..627ee02307 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,10 @@ *.pid *.prof *.pyc +*.pyo *~ *.sublime-* *.zip -build/ locale/ node_modules/ tmp/ @@ -44,4 +44,5 @@ writeup/ /TAGS .tags /kalite/i18n/static/ -ghostdriver.log \ No newline at end of file +ghostdriver.log +/kalite/_built.touch diff --git a/docs/DJANGO-CHANGES.txt b/docs/DJANGO-CHANGES.txt index 5daece9b11..6d6f127424 100644 --- a/docs/DJANGO-CHANGES.txt +++ b/docs/DJANGO-CHANGES.txt @@ -98,3 +98,14 @@ index 2b8e801..6349ed5 100644 + TestRunner = get_runner(settings, options.get('testrunner')) options['verbosity'] = int(options.get('verbosity')) + +We extend clean_pyc so it will be automatically skipped when built by the build process + def handle_noargs(self, **options): ++ from django.conf import settings ++ if settings.BUILT: ++ settings.LOG.info("Installation built by build process; skipping clean_pyc") ++ return ++ + project_root = options.get("path", None) + if not project_root: + project_root = get_project_root() diff --git a/kalite/control_panel/templates/control_panel/facility_management.html b/kalite/control_panel/templates/control_panel/facility_management.html index d987087efe..9fd3c3e741 100644 --- a/kalite/control_panel/templates/control_panel/facility_management.html +++ b/kalite/control_panel/templates/control_panel/facility_management.html @@ -31,12 +31,18 @@ altField: "#start_standard_date", altFormat: "yy-mm-dd", autoclose: true, + changeMonth: true, + changeYear: true, + showButtonPanel: true }); $('#end_date_select').datepicker({ dateFormat: "d MM yy", altField: "#end_standard_date", - altFormat: "yy-mm-dd" + altFormat: "yy-mm-dd", + changeMonth: true, + changeYear: true, + showButtonPanel: true }); }); diff --git a/kalite/control_panel/templates/control_panel/zone_management.html b/kalite/control_panel/templates/control_panel/zone_management.html index 0785dd555d..e0584919bc 100644 --- a/kalite/control_panel/templates/control_panel/zone_management.html +++ b/kalite/control_panel/templates/control_panel/zone_management.html @@ -164,8 +164,13 @@

{% trans "Devices" %}
{% trans "A device is a KA Lite installatio {% if device.is_own_device %} - - {% trans "Register device" %} + + {% if device.is_registered %} + + {% else %} + {% trans "Register device" %} + {% endif %} + {% if clock_set %} / set clock diff --git a/kalite/control_panel/views.py b/kalite/control_panel/views.py index 334273af63..1548e86a5d 100644 --- a/kalite/control_panel/views.py +++ b/kalite/control_panel/views.py @@ -215,9 +215,8 @@ def group_report(request, facility, group_id=None, zone_id=None): return context -@facility_required @require_authorized_admin -@render_to_csv(["students", "coaches"], key_label="user_id", order="stacked") +@render_to_csv(["students"], key_label="user_id", order="stacked") def facility_management_csv(request, facility, group_id=None, zone_id=None, frequency=None, period_start="", period_end="", user_type=None): """NOTE: THIS IS NOT A VIEW FUNCTION""" assert request.method == "POST", "facility_management_csv must be accessed via POST" @@ -227,7 +226,7 @@ def facility_management_csv(request, facility, group_id=None, zone_id=None, freq if not form.is_valid(): raise Exception(_("Error parsing date range: %(error_msg)s. Please review and re-submit.") % form.errors.as_data()) - frequency = frequency or request.GET.get("frequency", "months") + frequency = frequency or request.GET.get ("frequency", "months") period_start = period_start or form.data["period_start"] period_end = period_end or form.data["period_end"] (period_start, period_end) = _get_date_range(frequency, period_start, period_end) @@ -237,15 +236,15 @@ def facility_management_csv(request, facility, group_id=None, zone_id=None, freq context = control_panel_context(request, zone_id=zone_id, facility_id=facility.id) group = group_id and get_object_or_None(FacilityGroup, id=group_id) groups = FacilityGroup.objects.filter(facility=context["facility"]).order_by("name") - coaches = get_users_from_group(user_type="coaches", group_id=group_id, facility=facility) + # coaches = get_users_from_group(user_type="coaches", group_id=group_id, facility=facility) students = get_users_from_group(user_type="students", group_id=group_id, facility=facility) (student_data, group_data) = _get_user_usage_data(students, groups, group_id=group_id, period_start=period_start, period_end=period_end) - (coach_data, coach_group_data) = _get_user_usage_data(coaches, period_start=period_start, period_end=period_end) + # (coach_data, coach_group_data) = _get_user_usage_data(coaches, period_start=period_start, period_end=period_end) context.update({ "students": student_data, # raw data - "coaches": coach_data, # raw data + # "coaches": coach_data, # raw data }) return context @@ -378,10 +377,9 @@ def _get_user_usage_data(users, groups=None, period_start=None, period_end=None, user_data = OrderedDict() group_data = OrderedDict() - # Make queries efficiently exercise_logs = ExerciseLog.objects.filter(user__in=users, complete=True) - video_logs = VideoLog.objects.filter(user__in=users) + video_logs = VideoLog.objects.filter(user__in=users, total_seconds_watched__gt=0) login_logs = UserLogSummary.objects.filter(user__in=users) # filter results @@ -392,8 +390,7 @@ def _get_user_usage_data(users, groups=None, period_start=None, period_end=None, if period_end: exercise_logs = exercise_logs.filter(completion_timestamp__lte=period_end) video_logs = video_logs.filter(completion_timestamp__lte=period_end) - login_logs = login_logs.filter(end_datetime__lte=period_end) - + login_logs = login_logs.filter(total_seconds__gt=0, start_datetime__lte=period_end) # Force results in a single query exercise_logs = list(exercise_logs.values("exercise_id", "user__pk")) @@ -408,7 +405,6 @@ def _get_user_usage_data(users, groups=None, period_start=None, period_end=None, user_data[user.pk]["username"] = user.username user_data[user.pk]["group"] = user.group - user_data[user.pk]["total_report_views"] = 0#report_stats["count__sum"] or 0 user_data[user.pk]["total_logins"] =0# login_stats["count__sum"] or 0 user_data[user.pk]["total_hours"] = 0#login_stats["total_seconds__sum"] or 0)/3600. diff --git a/kalite/distributed/settings.py b/kalite/distributed/settings.py index 8c59607417..7e386e6492 100644 --- a/kalite/distributed/settings.py +++ b/kalite/distributed/settings.py @@ -42,6 +42,7 @@ def USER_FACING_PORT(): "fle_utils.config", "fle_utils.chronograph", "fle_utils.django_utils", # templatetags + "fle_utils.build", "kalite.facility", # must come first, all other apps depend on this one. "kalite.control_panel", # in both apps "kalite.coachreports", # in both apps; reachable on central via control_panel diff --git a/kalite/main/admin.py b/kalite/main/admin.py index 69f7ab13da..90b0cddf5c 100644 --- a/kalite/main/admin.py +++ b/kalite/main/admin.py @@ -13,8 +13,8 @@ class VideoLogAdmin(admin.ModelAdmin): admin.site.register(VideoLog, VideoLogAdmin) class ExerciseLogAdmin(admin.ModelAdmin): - list_display = ("exercise_id", "user", "language", "streak_progress", "complete",) - list_filter = ("exercise_id", "user", "language", "complete",) + list_display = ("exercise_id", "user", "language", "streak_progress", "completion_timestamp", "complete",) + list_filter = ("completion_timestamp", "complete", "language", "exercise_id", "user",) admin.site.register(ExerciseLog, ExerciseLogAdmin) class UserLogAdmin(admin.ModelAdmin): diff --git a/kalite/manage.py b/kalite/manage.py index 17fc24d7e5..7dab389102 100755 --- a/kalite/manage.py +++ b/kalite/manage.py @@ -5,9 +5,21 @@ import sys import warnings + +def clean_pyc(PROJECT_PATH): + for root, dirs, files in os.walk(os.path.join(PROJECT_PATH, "..")): + for pyc_file in glob.glob(os.path.join(root, "*.py[oc]")): + try: + os.remove(pyc_file) + except: + pass + if __name__ == "__main__": import warnings + BUILD_INDICATOR_FILE = os.path.join(".", "_built.touch") + BUILT = os.path.exists(BUILD_INDICATOR_FILE) # whether this installation was processed by the build server + # We are overriding a few packages (like Django) from the system path. # Suppress those warnings warnings.filterwarnings('ignore', message=r'Module .*? is being added to sys\.path', append=True) @@ -20,7 +32,6 @@ ] sys.path = [os.path.realpath(p) for p in PROJECT_PYTHON_PATHS] + sys.path - ######################## # kaserve ######################## @@ -41,12 +52,8 @@ ######################## # Manually clean all pyc files before entering any real codepath - for root, dirs, files in os.walk(os.path.join(PROJECT_PATH, "..")): - for pyc_file in glob.glob(os.path.join(root, "*.pyc")): - try: - os.remove(pyc_file) - except: - pass + if not BUILT: + clean_pyc(PROJECT_PATH) ######################## diff --git a/kalite/settings.py b/kalite/settings.py index 6adbc11707..838062ec3d 100644 --- a/kalite/settings.py +++ b/kalite/settings.py @@ -16,7 +16,6 @@ def package_selected(package_name): ############################## # Basic setup ############################## - try: from local_settings import * import local_settings @@ -29,7 +28,6 @@ def package_selected(package_name): CENTRAL_SERVER = False # Hopefully will be removed soon. - ############################## # Basic setup of logging ############################## @@ -51,6 +49,9 @@ def package_selected(package_name): # Not really a Django setting, but we treat it like one--it's eeeeverywhere. PROJECT_PATH = os.path.realpath(getattr(local_settings, "PROJECT_PATH", os.path.dirname(os.path.realpath(__file__)))) + "/" +BUILD_INDICATOR_FILE = os.path.join(PROJECT_PATH, "_built.touch") +BUILT = os.path.exists(BUILD_INDICATOR_FILE) # whether this installation was processed by the build server + LOCALE_PATHS = getattr(local_settings, "LOCALE_PATHS", (PROJECT_PATH + "/../locale",)) LOCALE_PATHS = tuple([os.path.realpath(lp) + "/" for lp in LOCALE_PATHS]) @@ -103,10 +104,15 @@ def package_selected(package_name): "django.contrib.messages", "django.contrib.sessions", "django_extensions", # needed for clean_pyc (testing) - "fle_utils.testing", - "kalite.testing", "kalite.distributed", -) + getattr(local_settings, 'INSTALLED_APPS', tuple()) +) + +if not BUILT: + INSTALLED_APPS += ( + "fle_utils.testing", + "kalite.testing", + ) + getattr(local_settings, 'INSTALLED_APPS', tuple()) + MIDDLEWARE_CLASSES = ( "django.contrib.messages.middleware.MessageMiddleware", # needed for django admin "django_snippets.session_timeout_middleware.SessionIdleTimeout", diff --git a/kalite/version.py b/kalite/version.py index f0a3dfd2d3..e1836bdeab 100644 --- a/kalite/version.py +++ b/kalite/version.py @@ -13,7 +13,7 @@ "admins": [], }, "bugs_fixed": { - "all": ["fix to handle multiple psutil versions", "fix to handle multiple psutil versions"], + "all": ["fix to handle multiple psutil versions", "use POST requests when deleting student groups"], "students": [], "coaches": [], "admins": [], diff --git a/python-packages/django_extensions/management/commands/clean_pyc.py b/python-packages/django_extensions/management/commands/clean_pyc.py index ee8c9511aa..b7e083ffd8 100644 --- a/python-packages/django_extensions/management/commands/clean_pyc.py +++ b/python-packages/django_extensions/management/commands/clean_pyc.py @@ -18,6 +18,11 @@ class Command(NoArgsCommand): requires_model_validation = False def handle_noargs(self, **options): + from django.conf import settings + if settings.BUILT: + settings.LOG.info("Installation built by build process; skipping clean_pyc") + return + project_root = options.get("path", None) if not project_root: project_root = get_project_root() diff --git a/python-packages/fle_utils/build/__init__.py b/python-packages/fle_utils/build/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python-packages/fle_utils/build/management/__init__.py b/python-packages/fle_utils/build/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python-packages/fle_utils/build/management/commands/__init__.py b/python-packages/fle_utils/build/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/python-packages/fle_utils/build/management/commands/generate_blacklist.py b/python-packages/fle_utils/build/management/commands/generate_blacklist.py new file mode 100644 index 0000000000..7b7cbc6fa7 --- /dev/null +++ b/python-packages/fle_utils/build/management/commands/generate_blacklist.py @@ -0,0 +1,151 @@ +import fnmatch +import os +import sys +import warnings +from datetime import datetime +from threading import Thread +from time import sleep, time +from optparse import make_option + +from django.conf import settings; logging = settings.LOG +from django.core.management.base import BaseCommand, CommandError +from django.core.management import call_command +from django.utils.importlib import import_module +from django.utils._os import upath + +KA_LITE_PATH = os.path.abspath(os.path.join(settings.PROJECT_PATH, "..")) + + +def make_path_relative(path): + if path.startswith(KA_LITE_PATH): + path = path[len(KA_LITE_PATH)+1:] + return path + + +def get_app_subdirectory_paths(subdir): + paths = [] + for appname in settings.INSTALLED_APPS: + app = import_module(appname) + subdirpath = os.path.join(os.path.dirname(upath(app.__file__)), subdir) + if os.path.exists(subdirpath): + # paths.append(make_path_relative(subdirpath)) + paths.append(subdirpath) + return paths + + +def get_paths_matching_pattern(pattern, starting_directory=KA_LITE_PATH): + paths = [] + for root, dirs, files in os.walk(KA_LITE_PATH): + # root = make_path_relative(root) + paths += [os.path.join(root, d) for d in dirs if fnmatch.fnmatch(d, pattern)] + paths += [os.path.join(root, f) for f in files if fnmatch.fnmatch(f, pattern)] + return paths + + +def get_paths_ending_with(substring, starting_directory=KA_LITE_PATH): + paths = [] + for root, dirs, files in os.walk(KA_LITE_PATH): + # root = make_path_relative(root) + paths += [os.path.join(root, d) for d in dirs if os.path.join(root, d).endswith(substring)] + paths += [os.path.join(root, f) for f in files if os.path.join(root, f).endswith(substring)] + return paths + + +def get_blacklist(removeunused=False, exclude_patterns=[], removestatic=False, removetests=False, removei18n=False, removekhan=False, **kwargs): + + blacklist = [] + + if removeunused: + blacklist += get_paths_ending_with("perseus/src") + blacklist += get_paths_matching_pattern(".git") + blacklist += get_paths_matching_pattern(".gitignore") + blacklist += get_paths_matching_pattern("requirements.txt") + blacklist += [ + "python-packages/postmark" + "python-packages/fle_utils/feeds", + "python-packages/announcements", + "python-packages/playground", + "python-packages/tastypie/templates", + "python-packages/tastypie/management", + "python-packages/django/contrib/admindocs", + "python-packages/django/contrib/flatpages", + # "python-packages/django/contrib/sitemaps", + "python-packages/django/contrib/comments", + ] + # don't need i18n stuff for django admin, since nobody should be seeing it + blacklist += get_paths_matching_pattern("locale", starting_directory=os.path.join(KA_LITE_PATH, "python-packages/django")) + + for pattern in exclude_patterns: + blacklist += get_paths_matching_pattern(pattern) + + if removestatic: + blacklist += get_app_subdirectory_paths("static") + + if removetests: + blacklist += get_app_subdirectory_paths("tests") + # blacklist += get_app_subdirectory_paths("tests.py") + blacklist += get_paths_matching_pattern("__tests__") + blacklist += [ + "kalite/static/khan-exercises/test", + "python-packages/selenium", + "kalite/testing", + ] + + if removei18n: + blacklist += get_paths_matching_pattern("locale") + blacklist += get_paths_matching_pattern("localeplanet") + blacklist += get_paths_matching_pattern("*.po") + blacklist += get_paths_matching_pattern("*.mo") + blacklist += get_paths_ending_with("jquery-ui/i18n") + + if removekhan: + blacklist += get_paths_matching_pattern("khan-exercises") + blacklist += get_paths_matching_pattern("perseus") + + # I want my paths absolute + blacklist = [os.path.abspath(os.path.join("..", path)) for path in blacklist] + return blacklist + + +class Command(BaseCommand): + args = "" + help = "Outputs a blacklist of files not to include when distributing for production." + + option_list = BaseCommand.option_list + ( + make_option('', '--removeunused', + action='store_true', + dest='removeunused', + default=False, + help='Exclude a number of files not currently being used at all'), + make_option('-e', '--exclude', + action='append', + dest='exclude_patterns', + default=[], + help='Exclude files matching a pattern (e.g. with a certain extension). Can be repeated to include multiple patterns.'), + make_option('', '--removestatic', + action='store_true', + dest='removestatic', + default=False, + help='Exclude static files in INSTALLED_APPS (be sure to run collectstatic)'), + make_option('', '--removetests', + action='store_true', + dest='removetests', + default=False, + help='Exclude tests folders in INSTALLED_APPS'), + make_option('', '--removei18n', + action='store_true', + dest='removei18n', + default=False, + help='Exclude locale and other i18n files/folders'), + make_option('', '--removekhan', + action='store_true', + dest='removekhan', + default=False, + help='Exclude khan-exercises, perseus, and other KA-specific stuff'), + ) + + def handle( self, *args, **options ): + + print "\n".join(get_blacklist(**options)) + + # python -O /usr/lib/python2.6/compileall.py . diff --git a/python-packages/fle_utils/settingshelper.py b/python-packages/fle_utils/settingshelper.py index 8f6a840cf3..078a31582f 100644 --- a/python-packages/fle_utils/settingshelper.py +++ b/python-packages/fle_utils/settingshelper.py @@ -83,4 +83,3 @@ def import_installed_app_settings(installed_apps, global_vars, cur_app="__root__ processed_apps=processed_apps) global_vars.update({"__file__": this_filepath}) # Set __file__ back to the project settings file - diff --git a/static-libraries/css/ui-datepicker.css b/static-libraries/css/ui-datepicker.css index ca9d813d41..8311d862c5 100755 --- a/static-libraries/css/ui-datepicker.css +++ b/static-libraries/css/ui-datepicker.css @@ -270,7 +270,7 @@ } .ui-widget-content { border: 1px solid #aaaaaa; - background: #ffffff url("images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x; + background: #ffffff url("jquery-ui/images/ui-bg_flat_75_ffffff_40x100.png") 50% 50% repeat-x; color: #222222; } .ui-widget-content a { @@ -278,7 +278,7 @@ } .ui-widget-header { border: 1px solid #aaaaaa; - background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; + background: #cccccc url("jquery-ui/images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x; color: #222222; font-weight: bold; } @@ -292,7 +292,7 @@ .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3; - background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; + background: #e6e6e6 url("jquery-ui/images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x; font-weight: normal; color: #555555; } @@ -309,7 +309,7 @@ .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999; - background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; + background: #dadada url("jquery-ui/images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x; font-weight: normal; color: #212121; } @@ -328,7 +328,7 @@ .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa; - background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; + background: #ffffff url("jquery-ui/images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x; font-weight: normal; color: #212121; } @@ -345,7 +345,7 @@ .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { border: 1px solid #fcefa1; - background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; + background: #fbf9ee url("jquery-ui/images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x; color: #363636; } .ui-state-highlight a, @@ -357,7 +357,7 @@ .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error { border: 1px solid #cd0a0a; - background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; + background: #fef1ec url("jquery-ui/images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x; color: #cd0a0a; } .ui-state-error a, @@ -403,27 +403,27 @@ } .ui-icon, .ui-widget-content .ui-icon { - background-image: url("images/ui-icons_222222_256x240.png"); + background-image: url("jquery-ui/images/ui-icons_222222_256x240.png"); } .ui-widget-header .ui-icon { - background-image: url("images/ui-icons_222222_256x240.png"); + background-image: url("jquery-ui/images/ui-icons_222222_256x240.png"); } .ui-state-default .ui-icon { - background-image: url("images/ui-icons_888888_256x240.png"); + background-image: url("jquery-ui/images/ui-icons_888888_256x240.png"); } .ui-state-hover .ui-icon, .ui-state-focus .ui-icon { - background-image: url("images/ui-icons_454545_256x240.png"); + background-image: url("jquery-ui/images/ui-icons_454545_256x240.png"); } .ui-state-active .ui-icon { - background-image: url("images/ui-icons_454545_256x240.png"); + background-image: url("jquery-ui/images/ui-icons_454545_256x240.png"); } .ui-state-highlight .ui-icon { - background-image: url("images/ui-icons_2e83ff_256x240.png"); + background-image: url("jquery-ui/images/ui-icons_2e83ff_256x240.png"); } .ui-state-error .ui-icon, .ui-state-error-text .ui-icon { - background-image: url("images/ui-icons_cd0a0a_256x240.png"); + background-image: url("jquery-ui/images/ui-icons_cd0a0a_256x240.png"); } /* positioning */ @@ -636,14 +636,14 @@ /* Overlays */ .ui-widget-overlay { - background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + background: #aaaaaa url("jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; opacity: .3; filter: Alpha(Opacity=30); } .ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; - background: #aaaaaa url("images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; + background: #aaaaaa url("jquery-ui/images/ui-bg_flat_0_aaaaaa_40x100.png") 50% 50% repeat-x; opacity: .3; filter: Alpha(Opacity=30); border-radius: 8px;