def get_download_urls(request): base_url = "%s://%s" % ("https" if request.is_secure() else "http", request.get_host()) # TODO: once Dylan makes all subtitle languages available, # don't hard-code this. download_sizes = { "en": 19.8, } downloads = {} for locale, size in download_sizes.iteritems(): urlargs = { "version": kalite.version.VERSION, "platform": "all", "locale": locale } downloads[locale] = { "display_name": "", # Will fill in when language list from subtitles is available. "size": size, "url": "%s%s" % (base_url, reverse("download_kalite_public", kwargs=urlargs)), } return JsonResponse(downloads)
def device_counters(data, session): device_counters = get_device_counters( zone=session.client_device.get_zone()) return JsonResponse({ "device_counters": device_counters, })
def learner_logs(request): lang = request.language page = request.GET.get("page", 1) limit = request.GET.get("limit", 50) # Look back a week by default time_window = request.GET.get("time_window", 7) start_date = request.GET.get("start_date", None) end_date = request.GET.get("end_date", None) topic_ids = request.GET.getlist("topic_id", []) learners = get_learners_from_GET(request) pages = int(ceil(len(learners)/float(limit))) if page*limit < len(learners): learners = learners[(page - 1)*limit: page*limit] log_types = request.GET.getlist("log_type", ["exercise", "video", "content"]) output_logs = [] output_objects = [] end_date = datetime.datetime.strptime(end_date,'%Y/%m/%d') if end_date else datetime.datetime.now() start_date = datetime.datetime.strptime(start_date,'%Y/%m/%d') if start_date else end_date - datetime.timedelta(time_window) for log_type in log_types: LogModel, fields, id_field, obj_ids, objects = return_log_type_details(log_type, topic_ids) log_objects = LogModel.objects.filter(user__in=learners, **obj_ids).values(*fields) if not topic_ids: topic_objects = log_objects.filter(latest_activity_timestamp__gte=start_date, latest_activity_timestamp__lte=end_date) if topic_objects.count() == 0: topic_objects = log_objects objects = dict([(obj[id_field], get_content_cache(language=lang).get(obj[id_field], get_exercise_cache(language=lang).get(obj[id_field]))) for obj in topic_objects]).values() output_objects.extend(objects) output_logs.extend(log_objects) return JsonResponse({ "logs": output_logs, "contents": output_objects, # Sometimes 'learners' gets collapsed to a list from the Queryset. This insures against that eventuality. "learners": [{ "first_name": learner.first_name, "last_name": learner.last_name, "username": learner.username, "pk": learner.pk } for learner in learners], "page": page, "pages": pages, "limit": limit })
def get_kalite_version(request): assert kalite.version.VERSION in kalite.version.VERSION_INFO def versionkey(v): '''sorts a version. For now, it sorts them by release date. It returns a number in the hundreds range to make space for subsorts, such as version number. ''' version, vdata = v date = datetime.datetime.strptime(vdata['release_date'], "%Y/%m/%d") return date.toordinal( ) / 1000 # divide by 1000 to turn into 100s range request_version = request.GET.get( "current_version", "0.10.0") # default to first version that can understand this. needed_updates = [ version for version, _ in sorted(kalite.version.VERSION_INFO.iteritems(), key=versionkey) if StrictVersion(request_version) < StrictVersion(version) ] # versions are nice--they sort by string return JsonResponse({ "version": kalite.version.VERSION, "version_info": OrderedDict([(v, kalite.version.VERSION_INFO[v]) for v in needed_updates]), })
def set_server_or_user_default_language(request): if request.method == 'GET': return JsonResponseMessageError(_( "Can only handle default language changes through POST requests"), status=405) elif request.method == 'POST': data = json.loads( request.raw_post_data ) # POST is getting interpreted wrong again by Django lang_code = data['lang'] if request.is_django_user and lang_code != get_default_language(): logging.debug("setting server default language to %s" % lang_code) set_default_language(lang_code) elif not request.is_django_user and request.is_logged_in and lang_code != request.session[ "facility_user"].default_language: logging.debug("setting user default language to %s" % lang_code) request.session["facility_user"].default_language = lang_code request.session["facility_user"].save() if lang_code != request.session.get("default_language"): logging.debug("setting session language to %s" % lang_code) request.session["default_language"] = lang_code set_request_language(request, lang_code) return JsonResponse({"status": "OK"})
def set_server_or_user_default_language(request): """This function sets the default language for either the server or user. It is accessed via HTTP POST or GET. Required Args (POST or GET): lang (str): any supported ISO 639-1 language code Optional Args (GET): returnUrl (str): the URL to redirect the client to after setting the language allUsers (bool): when true, set the the default language for all users, when false or missing, set the language for current user Returns: JSON status, unless a returnUrl is provided, in which case it returns a redirect when successful Example: To set the current user's language to Spanish and send them to the Math section, you could use the following link: /api/i18n/set_default_language/?lang=es&returnUrl=/learn/khan/math """ returnUrl = '' allUsers = '' # GET requests are used by RACHEL to jump to a specific page with the # language already set so the user doesn't have to. if request.method == 'GET': data = request.GET if not 'lang' in data: return redirect('/') if 'returnUrl' in data: returnUrl = data['returnUrl'] if 'allUsers' in data: allUsers = data['allUsers'] elif request.method == 'POST': data = json.loads(request.raw_post_data) # POST is getting interpreted wrong again by Django lang_code = data['lang'] if allUsers or (request.is_django_user and lang_code != get_default_language()): logging.debug("setting server default language to %s" % lang_code) set_default_language(lang_code) elif not request.is_django_user and request.is_logged_in and lang_code != request.session["facility_user"].default_language: logging.debug("setting user default language to %s" % lang_code) request.session["facility_user"].default_language = lang_code request.session["facility_user"].save() if lang_code != request.session.get("default_language"): logging.debug("setting session language to %s" % lang_code) request.session["default_language"] = lang_code set_request_language(request, lang_code) if not returnUrl: return JsonResponse({"status": "OK"}) else: return redirect(returnUrl)
def get_update_topic_tree(request): parent = request.GET.get("parent") lang_code = request.GET.get( "lang" ) or request.language # Get annotations for the current language. return JsonResponse( get_topic_update_nodes(parent=parent, language=lang_code))
def delete_language_pack(request): """ API endpoint for deleting language pack which fetches the language code (in delete_id) which has to be deleted. That particular language folders are deleted and that language gets removed. """ lang_code = simplejson.loads(request.body or "{}").get("lang") delete_language(lang_code) return JsonResponse({"success": _("Successfully deleted language pack for %(lang_name)s.") % {"lang_name": get_language_name(lang_code)}})
def narrative_view(request, narrative_id): """ :param request: the request :param narrative_id: the narrative id, a url to be matched :return: a serialized JSON blob of the narrative dict """ filename = os.path.join(settings.CONTENT_DATA_PATH, "narratives") narratives = open_json_or_yml(filename) the_narrative = {} for key, narr in narratives.iteritems(): exp = re.compile(key) if exp.search(narrative_id): the_narrative[key] = narr break return JsonResponse(the_narrative)
def get_annotated_topic_tree(request, lang_code=None): call_command( "videoscan" ) # Could potentially be very slow, blocking request... but at least it's via an API request! lang_code = lang_code or request.language # Get annotations for the current language. statusdict = dict( VideoFile.objects.values_list("youtube_id", "percent_complete")) return JsonResponse( annotate_topic_tree(softload_json(TOPICS_FILEPATHS.get(CHANNEL), logger=logging.debug, raises=False), statusdict=statusdict, lang_code=lang_code))
def device_upload(data, session): """This device is getting device-related objects from another device""" # TODO(jamalex): check that the uploaded devices belong to the client device's zone and whatnot # (although it will only save zones from here if centrally signed, and devices if registered in a zone) try: # Unserialize, knowing that the models were serialized by a client of its given version. # dest_version assumed to be this device's version result = save_serialized_models(data.get("devices", "[]"), src_version=session.client_version) except Exception as e: logging.debug("Exception uploading devices (in api_views): %s" % e) result = {"error": e.message, "saved_model_count": 0} session.models_uploaded += result["saved_model_count"] session.errors += result.has_key("error") return JsonResponse(result)
def device_download(data, session): """This device is having its own devices downloaded""" zone = session.client_device.get_zone() devicezones = list( DeviceZone.all_objects.filter( zone=zone, device__in=data["devices"])) # including deleted devicezones devices = [devicezone.device for devicezone in devicezones] session.models_downloaded += len(devices) + len(devicezones) # Return the objects serialized to the version of the other device. return JsonResponse({ "devices": serialize(devices + devicezones, dest_version=session.client_version, ensure_ascii=False) })
def narrative_view(request, narrative_id): """ :param request: the request :param narrative_id: the narrative id, a url to be matched :return: a serialized JSON blob of the narrative dict """ the_narrative = {} for key, narr in NARRATIVES.iteritems(): exp = re.compile(key) if exp.search(narrative_id): the_narrative[key] = narr break if not the_narrative: return JsonResponseMessageWarning( _("No inline help is available for this page."), status=404) return JsonResponse(the_narrative)
def narrative_view(request, narrative_id): """ :param request: the request :param narrative_id: the narrative id, a url to be matched :return: a serialized JSON blob of the narrative dict """ filename = os.path.join(settings.CONTENT_DATA_PATH, "narratives") narratives = open_json_or_yml(filename) the_narrative = {} for key, narr in narratives.iteritems(): exp = re.compile(key) if exp.search(narrative_id): the_narrative[key] = narr break if not the_narrative: return JsonResponseMessageWarning(_("No inline help is available for this page."), status=404) return JsonResponse(the_narrative)
def content_recommender(request): """Populate response with recommendation(s)""" user_id = request.GET.get('user', None) user = request.session.get('facility_user') if not user: if request.user.is_authenticated() and request.user.is_superuser: user = get_object_or_404(FacilityUser, pk=user_id) else: return JsonResponseMessageError( "You are not authorized to view these recommendations.", status=401) resume = request.GET.get('resume', None) next = request.GET.get('next', None) explore = request.GET.get('explore', None) def set_bool_flag(flag_name, rec_dict): rec_dict[flag_name] = True return rec_dict # retrieve resume recommendation(s) and set resume boolean flag resume_recommendations = [ set_bool_flag("resume", rec) for rec in get_resume_recommendations(user, request) ] if resume else [] # retrieve next_steps recommendations, set next_steps boolean flag, and flatten results for api response next_recommendations = [ set_bool_flag("next", rec) for rec in get_next_recommendations(user, request) ] if next else [] # retrieve explore recommendations, set explore boolean flag, and flatten results for api response explore_recommendations = [ set_bool_flag("explore", rec) for rec in get_explore_recommendations(user, request) ] if explore else [] return JsonResponse(resume_recommendations + next_recommendations + explore_recommendations)
def model_upload(data, session): """This device is getting data-related objects from another device.""" if "models" not in data: return JsonResponseMessageError("Must provide models.", data={"saved_model_count": 0}, status=400) try: # Unserialize, knowing that the models were serialized by a client of its given version. # dest_version assumed to be this device's version result = save_serialized_models(data["models"], src_version=session.client_version) except Exception as e: print "Exception uploading models (in api_views): %s, %s, %s" % ( e.__class__.__name__, e.message, e.args) result = {"error": e.message, "saved_model_count": 0} session.models_uploaded += result["saved_model_count"] session.errors += result.has_key("error") return JsonResponse(result)
def model_download(data, session): """This device is having its own data downloaded""" if "device_counters" not in data: return JsonResponseMessageError("Must provide device counters.", data={"count": 0}, status=400) try: # Return the objects serialized to the version of the other device. result = get_serialized_models(data["device_counters"], zone=session.client_device.get_zone(), include_count=True, dest_version=session.client_version) except Exception as e: print "Exception downloading models (in api_views): %s, %s, %s" % ( e.__class__.__name__, e.message, e.args) result = {"error": e.message, "count": 0} session.models_downloaded += result["count"] session.errors += result.has_key("error") return JsonResponse(result)
def content_item(request, channel, content_id): language = request.language content = get_content_item(channel=channel, content_id=content_id, language=language) if not content: content = { "title": "Unavailable Content", "description": "This content is unavailable. Either it must be downloaded, or the url is incorrect.", "available": False, "kind": "Video", "id": "unavailable_content", "slug": "unavailable_content", "path": "unavailable_content" } if not content.get("available", False): if request.is_admin: # TODO(bcipolli): add a link, with querystring args that auto-checks this content in the topic tree messages.warning( request, _("This content was not found! You can download it by going to the Manage > Videos page." )) elif request.is_logged_in: messages.warning( request, _("This content was not found! Please contact your coach or an admin to have it downloaded." )) elif not request.is_logged_in: messages.warning( request, _("This content was not found! You must login as an admin/coach to download the content." )) content["messages"] = get_messages_for_api_calls(request) return JsonResponse(content)
def search_api(request, channel): query = request.GET.get("term") if query is None: return JsonResponseMessageError("No search term specified", status=404) query = query.lower() # search for topic, video or exercise with matching title matches, exact, pages = search_topic_nodes(query=query, channel=channel, language=request.language, page=1, items_per_page=15, exact=False) if not matches: messages.warning( request, _("Search completed, no content was found for your search. Try something else." )) return JsonResponse(matches)
def register_public_key_server_auto(request): """This function allows an anonymous client to request a device key to be associated with a new zone. This allows registration to occur without a single login; the device will be associated with a headless zone. """ public_key = urllib.unquote(request.GET.get("device_key", "")) if RegisteredDevicePublicKey.objects.filter(public_key=public_key): return HttpResponseForbidden("Device is already registered.") # Create some zone. zone = Zone(name="Zone for public key %s" % public_key[:50]) zone.save() # Add an association between a device 's public key and this zone, # so that when registration is attempted by the distributed server # with this key, it will register and receive this zone info. RegisteredDevicePublicKey(zone=zone, public_key=public_key).save() # Report success return JsonResponse({})
def set_server_or_user_default_language(request): returnUrl = '' allUsers = '' if request.method == 'GET': data = request.GET if 'returnUrl' in data: returnUrl = data['returnUrl'] if 'allUsers' in data: allUsers = data['allUsers'] elif request.method == 'POST': data = json.loads( request.raw_post_data ) # POST is getting interpreted wrong again by Django lang_code = data['lang'] if allUsers or (request.is_django_user and lang_code != get_default_language()): logging.debug("setting server default language to %s" % lang_code) set_default_language(lang_code) elif not request.is_django_user and request.is_logged_in and lang_code != request.session[ "facility_user"].default_language: logging.debug("setting user default language to %s" % lang_code) request.session["facility_user"].default_language = lang_code request.session["facility_user"].save() if lang_code != request.session.get("default_language"): logging.debug("setting session language to %s" % lang_code) request.session["default_language"] = lang_code set_request_language(request, lang_code) if returnUrl == '': return JsonResponse({"status": "OK"}) else: return redirect(returnUrl)
def topic_tree(request, channel): parent = request.GET.get("parent") return JsonResponse( get_topic_tree(channel=channel, language=request.language, parent=parent))
def check_update_progress(request, process_log): """ API endpoint for getting progress data on downloads. """ return JsonResponse(_process_log_to_dict(process_log))
def destroy_session(data, session): session.closed = True return JsonResponse({})
def installed_language_packs(request): return JsonResponse(get_installed_language_packs(force=True).values())
def assessment_item(request, assessment_item_id): assessment_item_dict = get_assessment_item_data( channel=getattr(request, "channel", "khan"), language=getattr(request, "language", "en"), assessment_item_id=assessment_item_id) return JsonResponse(assessment_item_dict)
def get_server_info(request): """This function is used to check connection to central or local server and also to get specific data from server. Args: The http request. Returns: A json object containing general data from the server. """ device = None zone = None device_info = {"status": "OK", "invalid_fields": []} for field in request.GET.get("fields", "").split(","): if field == "version": device = device or Device.get_own_device() device_info[field] = device.get_version() elif field == "device_name": device = device or Device.get_own_device() device_info[field] = device.name elif field == "device_description": device = device or Device.get_own_device() device_info[field] = device.description elif field == "device_description": device = device or Device.get_own_device() device_info[field] = device.description elif field == "device_id": device = device or Device.get_own_device() device_info[field] = device.id elif field == "zone_name": if settings.CENTRAL_SERVER: continue device = device or Device.get_own_device() zone = zone or device.get_zone() device_info[field] = zone.name if zone else None elif field == "zone_id": if settings.CENTRAL_SERVER: continue device = device or Device.get_own_device() zone = zone or device.get_zone() device_info[field] = zone.id if zone else None elif field == "online": if settings.CENTRAL_SERVER: device_info[field] = True else: device_info[field] = am_i_online() elif field: # the field isn't one we know about, so add it to the list of invalid fields device_info["invalid_fields"].append(field) return JsonResponse(device_info)
def create_session(request): data = simplejson.loads(request.body or "{}") if "client_nonce" not in data: return JsonResponseMessageError("Client nonce must be specified.", status=400) if len(data["client_nonce"]) != 32 or re.match("[^0-9a-fA-F]", data["client_nonce"]): return JsonResponseMessageError( "Client nonce is malformed (must be 32-digit hex).", status=400) if "client_device" not in data: return JsonResponseMessageError("Client device must be specified.", status=400) if "server_nonce" not in data: if SyncSession.objects.filter( client_nonce=data["client_nonce"]).count(): return JsonResponseMessageError( "Session already exists; include server nonce and signature.", status=401) session = SyncSession() session.client_nonce = data["client_nonce"] session.client_os = data.get("client_os", "") session.client_version = data.get("client_version", "") session.ip = get_request_ip(request) try: client_device = Device.objects.get(pk=data["client_device"]) session.client_device = client_device except Device.DoesNotExist: # This is the codepath for unregistered devices trying to start a session. # This would only get hit, however, if they manually run syncmodels. # But still, good to keep track of! UnregisteredDevicePing.record_ping(id=data["client_device"], ip=session.ip) return JsonResponseMessageError( "Client device matching id could not be found. (id=%s)" % data["client_device"], status=400) session.server_nonce = uuid.uuid4().hex session.server_device = Device.get_own_device() if session.client_device.pk == session.server_device.pk: return JsonResponseMessageError( "I know myself when I see myself, and you're not me.", status=401) session.save() else: try: session = SyncSession.objects.get( client_nonce=data["client_nonce"]) except SyncSession.DoesNotExist: return JsonResponseMessageError( "Session with specified client nonce could not be found.", status=401) if session.server_nonce != data["server_nonce"]: return JsonResponseMessageError( "Server nonce did not match saved value.", status=401) if not data.get("signature", ""): return JsonResponseMessageError("Must include signature.", status=400) if not session.verify_client_signature(data["signature"]): return JsonResponseMessageError("Signature did not match.", status=401) session.verified = True session.save() # Return the serialized session, in the version intended for the other device return JsonResponse({ "session": serialize([session], dest_version=session.client_version, ensure_ascii=False, sign=False, increment_counters=False), "signature": session.sign(), })
def register_device(request): """Receives the client device info from the distributed server. Tries to register either because the device has been pre-registered, or because it has a valid INSTALL_CERTIFICATE.""" # attempt to load the client device data from the request data data = simplejson.loads(request.body or "{}") if "client_device" not in data: return JsonResponseMessageError( "Serialized client device must be provided.", status=400) try: # When hand-shaking on the device models, since we don't yet know the version, # we have to just TRY with our own version. # # This is currently "central server" code, so # this will only fail (currently) if the central server version # is less than the version of a client--something that should never happen try: local_version = Device.get_own_device().get_version() models = deserialize(data["client_device"], src_version=local_version, dest_version=local_version) except db_models.FieldDoesNotExist as fdne: raise Exception( "Central server version is lower than client version. This is ... impossible!" ) client_device = models.next().object except Exception as e: return JsonResponseMessageError( "Could not decode the client device model: %s" % e, code=EC.CLIENT_DEVICE_CORRUPTED, status=400) # Validate the loaded data if not isinstance(client_device, Device): return JsonResponseMessageError( "Client device must be an instance of the 'Device' model.", code=EC.CLIENT_DEVICE_NOT_DEVICE) try: if not client_device.verify(): # We've been getting this verification error a lot, even when we shouldn't. Send more details to us by email so we can diagnose. msg = "\n\n".join([ request.body, client_device._hashable_representation(), str(client_device.validate()), client_device.signed_by_id, client_device.id, str(request) ]) send_mail("Client device did not verify", msg, "*****@*****.**", ["*****@*****.**"]) return JsonResponseMessageError( "Client device must be self-signed with a signature matching its own public key!", code=EC.CLIENT_DEVICE_INVALID_SIGNATURE) except Exception as e: # Can't properly namespace to a particular Exception here, since the only reason we would be getting here is # that what should be proper exception namespacing in code being called isn't correctly catching this exception msg = "\n\n".join([ request.body, client_device._hashable_representation(), "Exception: %s" % e, str(type(e)), client_device.signed_by_id, client_device.id, str(request) ]) send_mail("Exception while verifying client device", msg, "*****@*****.**", ["*****@*****.**"]) return JsonResponseMessageError( "Client device must be self-signed with a signature matching its own public key!", code=EC.CLIENT_DEVICE_INVALID_SIGNATURE) try: zone = register_self_registered_device(client_device, models, data) except Exception as e: if e.args[0] == "Client not yet on zone.": zone = None else: # Client not on zone: allow fall-through via "old route" # This is the codepath for unregistered devices trying to start a session. # This would only get hit, however, if they visit the registration page. # But still, good to keep track of! UnregisteredDevicePing.record_ping(id=client_device.id, ip=get_request_ip(request)) return JsonResponseMessageError( "Failed to validate the chain of trust (%s)." % e, code=EC.CHAIN_OF_TRUST_INVALID, status=500) if not zone: # old code-path try: registration = RegisteredDevicePublicKey.objects.get( public_key=client_device.public_key) if not registration.is_used(): registration.use() # Use the RegisteredDevicePublicKey, now that we've initialized the device and put it in its zone zone = registration.zone except RegisteredDevicePublicKey.DoesNotExist: try: # A redirect loop here is also possible, if a Device exists in the central server database # corresponding to the client_device, but no corresponding RegisteredDevicePublicKey exists device = Device.objects.get( public_key=client_device.public_key) return JsonResponseMessageError( "This device has already been registered", code=EC.DEVICE_ALREADY_REGISTERED, status=409) except Device.DoesNotExist: return JsonResponseMessageError( "Device registration with public key not found; login and register first?", code=EC.PUBLIC_KEY_UNREGISTERED, status=404) client_device.save(imported=True) try: device_zone = DeviceZone.objects.get(device=client_device, zone=zone) device_zone.save( ) # re-save, to give it a central server signature that will be honored by old clients except DeviceZone.DoesNotExist: device_zone = DeviceZone(device=client_device, zone=zone) device_zone.save( ) # create the DeviceZone for the new device, with an 'upgraded' signature # return our local (server) Device, its Zone, and the newly created DeviceZone, to the client # Note the order :) # # Addition: always back central server object--in case they didn't get it during install, # they need it for software updating. return JsonResponse( serialize([ Device.get_central_server(), Device.get_own_device(), zone, device_zone ], dest_version=client_device.version, ensure_ascii=False))
def force_sync(request): """ """ force_job("syncmodels") # now launches asynchronously return JsonResponse({})