def transfer_ownership(): # No matter if we are handling a GET or POST, we require these parameters. appid = request.values.get("appid") # Retrieve the application we want to view or edit. app = get_app(appid) if app is None: return render_template("composers/errors.html", message=gettext("App not found")), 404 # Make sure the logged in user owns the app. user = current_user() if app.owner != user: return render_template("composers/errors.html", message=gettext("Not Authorized: User does not own app")), 403 # Get the XMLSPEC bm = BundleManager.create_from_existing_app(app.data) spec = bm.get_gadget_spec() # Get the language lang = request.values.get("lang") if lang is None: return render_template("composers/errors.html", message=gettext("Lang not specified")), 400 # Verify that we are the owners of the language we are trying to transfer. owner_app = _db_get_lang_owner_app(spec, lang) if owner_app != app: return render_template("composers/errors.html", message=gettext("Not Authorized: App does not own language")), 403 # Get the possible apps to which we can concede ownership. apps = _db_get_spec_apps(spec) apps = [a for a in apps if a != app] # We received a POST request. We need to transfer the ownership. if request.method == "POST": # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message=gettext("Request does not seem to come from the right source (csrf check)")), 400 # Verify that we were passed the target app. targetapp = get_app(request.values.get("transfer")) if targetapp is None: return render_template("composers/errors.html", message=gettext("Target app not specified")), 400 # Verify that the target app is of the same spec. targetappdata = json.loads(targetapp.data) targetspec = targetappdata["spec"] if targetspec != spec: return render_template("composers/errors.html", message=gettext("Target app does not have the same spec")), 400 # Carry out the transfer. _db_transfer_ownership(lang, app, targetapp) # Redirect to selectlang. return redirect(url_for("translate.translate_selectlang", appid=app.unique_id)) # For GET return render_template("composers/translate/transfer_ownership.html", app=app, apps=apps, xmlspec=spec, lang=lang)
def test_autoaccept_post(self): # Check for 0 POST url = u"/composers/translate/config/autoaccept/" + self.tapp.unique_id rv = self.flask_app.post(url, data={"value": 0}) assert rv.status_code == 200 js = json.loads(rv.data) assert js["result"] == "success" assert js["value"] == False app = get_app(self.tapp.unique_id) appdata = json.loads(app.data) assert appdata["autoaccept"] == False # Check for 1 POST url = u"/composers/translate/config/autoaccept/" + self.tapp.unique_id rv = self.flask_app.post(url, data={"value": 1}) assert rv.status_code == 200 js = json.loads(rv.data) assert js["result"] == "success" assert js["value"] == True app = get_app(self.tapp.unique_id) appdata = json.loads(app.data) assert appdata["autoaccept"] == True # Check for INVALID POST url = u"/composers/translate/config/autoaccept/" + self.tapp.unique_id rv = self.flask_app.post(url, data={"value": 234124}) # Invalid value assert rv.status_code == 200 js = json.loads(rv.data) assert js["result"] == "error"
def delete(): # If GET we display the confirmation screen and do not actually delete it. if request.method == "GET": appid = request.args.get("appid") if not appid: return "appid not provided", 400 app = appstorage.get_app(appid) if app is None: return "App not found", 500 return render_template("composers/dummy/delete.html", app=app) # If POST we consider whether the user clicked Delete or Cancel in the confirmation screen. elif request.method == "POST": # If the user didn't click delete he probably clicked cancel. # We return to the Apps View page. if not "delete" in request.form: return redirect(url_for("user.apps.index")) appid = request.form.get("appid") if not appid: return "appid not provided", 400 app = appstorage.get_app(appid) if app is None: return "App not found", 500 appstorage.delete_app(app) flash(gettext("App successfully deleted."), "success") return redirect(url_for("user.apps.index"))
def app_xml(appid, group): """ app_xml(appid, group) Provided for end-users. This is the function that provides hosting for the gadget specs for a specified App. The gadget specs are actually dynamically generated, as every time a request is made the original XML is obtained and modified. @param appid: Identifier of the App. @param group: Group that will act as a filter. If, for instance, it is set to 14-18, then only Bundles that belong to that group will be shown. @return: XML of the modified Gadget Spec with the Locales injected, or an HTTP error code if an error occurs. """ app = get_app(appid) if app is None: return render_template("composers/errors.html", message="Error 404: App doesn't exist"), 404 # The composer MUST be 'translate' if app.composer != "translate": return render_template("composers/errors.html", message="Error 500: The composer for the specified App is not Translate"), 500 bm = BundleManager.create_from_existing_app(app.data) output_xml = bm.do_render_app_xml(appid, group) response = make_response(output_xml) response.mimetype = "application/xml" return response
def _cleanup(self): """ Does cleanup tasks in case the tests failed before. Can be invoked *before* and *after* the tests. """ self.flask_app.get("/") # This is required to create a context. Otherwise session etc don't exist. if self.firstApp is not None: app = api.get_app(self.firstApp.unique_id) if app is not None: api.delete_app(app) if self.secondApp is not None: app = api.get_app(self.secondApp.unique_id) if app is not None: api.delete_app(app)
def adapt_edit(appid): """ adapt_edit() Form-based user interface for editing the contents of an adaptor app. @return: The final app with all its fields stored in the database. """ if not appid: return "appid not provided", 400 # TODO: Improve this: do not load the whole thing. We just need the variables. app = appstorage.get_app(appid) if app is None: return "Error: App not found", 500 adaptor_types = [var for var in app.appvars if var.name == 'adaptor_type'] if not adaptor_types: return "Error: no attached adaptor_type variable" adaptor_type = adaptor_types[0].value if adaptor_type not in ADAPTORS: return "Error: adaptor %s not currently supported" % adaptor_type adaptor_plugin = ADAPTORS[adaptor_type]['adaptor'] return redirect(url_for(adaptor_plugin._edit_endpoint, appid=appid))
def test_create_app(self): app = api.create_app("UTApp", "dummy", None, "{}") assert app is not None assert app.name == "UTApp" id = app.unique_id # TODO: Probably no point for App to have two different unique ids. app = None app = api.get_app(id) assert app is not None assert app.name == "UTApp" assert app.owner == current_user()
def test_create_app(self): app = api.create_app("UTApp", "dummy", "{}") assert app is not None assert app.name == "UTApp" id = app.unique_id # TODO: Probably no point for App to have two different unique ids. app = None app = api.get_app(id) assert app is not None assert app.name == "UTApp" assert app.owner == current_user()
def _cleanup(self): """ Does cleanup tasks in case the tests failed before. Can be invoked *before* and *after* the tests. """ self.flask_app.get("/") # This is required to create a context. Otherwise session etc don't exist. self.login("testuser", "password") if self.firstApp is not None: app = api.get_app(self.firstApp) if app is not None: api.delete_app(app) self.login("testuser2", "password") if self.secondApp is not None: app = api.get_app(self.secondApp) if app is not None: api.delete_app(app) db.session.query(Spec).filter_by(url="TESTURL").delete()
def edit(): if request.method == "GET": appid = request.args.get("appid") if not appid: return "appid not provided", 400 app = appstorage.get_app(appid) if app is None: return "App not found", 500 data = json.loads(app.data) text = data["text"] return render_template("composers/dummy/edit.html", app=app, text=text) elif request.method == "POST": appid = request.form["appid"] text = request.form["text"] # Retrieve the app we're editing by its id. app = appstorage.get_app(appid) # Build our dummy composer JSON. data = { "dummy_version": 1, "text": text} appstorage.update_app_data(app, data) flash(gettext("Saved successfully"), "success") # TODO: Print a success message etc etc. # If the user clicked on saveexit we redirect to appview, otherwise # we stay here. if "saveexit" in request.form: return redirect(url_for("user.apps.index")) return render_template("composers/dummy/edit.html", app=app, text=text)
def test_change_description(self): """ Test that changing the description of the app works. """ post_url = u"/change/appdescription/" + self.tapp.unique_id rv = self.flask_app.post(post_url, data={"description": "A new App description"}) assert rv.status_code == 200 js = json.loads(rv.data) assert js["result"] == "success" # Ensure that the App Name changed. tapp = api.get_app(self.tapp.unique_id) assert tapp.description == "A new App description"
def test_change_name(self): """ Test that changing the name of the app works. """ post_url = u"/change/appname/" + self.tapp.unique_id rv = self.flask_app.post(post_url, data={"name": "RenamedApp"}) assert rv.status_code == 200 js = json.loads(rv.data) assert js["result"] == "success" # Ensure that the App Description changed. tapp = api.get_app(self.tapp.unique_id) assert tapp.name == "RenamedApp"
def wrapper(appid): # TODO: Improve this: do not load the whole thing. We just need the ID and so on. if not appid: return "appid not provided", 400 app = appstorage.get_app(appid) if app is None: return "App not found", 500 data = json.loads(app.data) adaptor_type = data["adaptor_type"] if adaptor_type != self.name: return "This Adaptor is not of this adaptor type", 400 return func(appid)
def adapt_preview(appid): """ adapt_preview(appid) Previews an application. You can preview the app of any user. # TODO: Eventually the preview feature should probably be merged into view_edit, through a read-only mode. LOGIN IS OPTIONAL FOR THIS METHOD. @param appid: Appid of the app to preview. """ if not appid: return render_template("composers/errors.html", message=gettext("appid not provided")), 400 # Check whether we are logged in. logged_in = current_user() is not None # TODO: Improve this: do not load the whole thing. We just need the variables. app = appstorage.get_app(appid) if app is None: return render_template("composers/errors.html", message=gettext("app not found")), 500 adaptor_types = [var for var in app.appvars if var.name == 'adaptor_type'] if not adaptor_types: return render_template("composers/errors.html", message=gettext("Error: no attached adaptor_type variable")), 500 adaptor_type = adaptor_types[0].value if adaptor_type not in ADAPTORS: return render_template("composers/errors.html", message=gettext("Error: adaptor %s not currently supported") % adaptor_type), 500 adaptor_plugin = ADAPTORS[adaptor_type]['adaptor'] # TODO: URLs seem to be dependent on adaptor type. This is meant to work with jsconfig at least. # Calculate the URL for the Preview iframe. app_url = url_for('%s.app_xml' % adaptor_type, app_id=appid, _external=True) preview_url = shindig_url("/gadgets/ifr?nocache=1&url=%s" % app_url) # Retrieve the URL from the appdata. appdata = json.loads(app.data) spec_url = appdata["url"] return render_template("composers/adapt/preview.html", logged_in=logged_in, app_id=appid, app_url=app_url, spec_url=spec_url, name=app.name, preview_url=preview_url)
def adapt_duplicate(appid): app = get_app(appid) if app is None: return render_template("composers/errors.html", message="Application not found") form = DuplicationForm() if form.validate_on_submit(): # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message="Request does not seem to come from the right source (csrf check)"), 400 existing_app = get_app_by_name(form.name.data) if existing_app: if not form.name.errors: form.name.errors = [] form.name.errors.append(lazy_gettext("You already have an application with this name")) else: new_app = create_app(form.name.data, 'adapt', app.spec.url, app.data) for appvar in app.appvars: # Copy every appvar for the original app as well. add_var(new_app, appvar.name, appvar.value) return redirect(url_for('.adapt_edit', appid=new_app.unique_id)) if not form.name.data: counter = 2 potential_name = '' while counter < 1000: potential_name = '%s (%s)' % (app.name, counter) existing_app = get_app_by_name(potential_name) if not existing_app: break counter += 1 form.name.data = potential_name return render_template("composers/adapt/duplicate.html", form=form, app=app)
def app_langfile(appid, langfile): """ app_langfile(appid, langfile, age) Provided for end-users. This is the function that provides hosting for the langfiles for a specified App. The langfiles are actually dynamically generated (the information is extracted from the Translate-specific information). @param appid: Appid of the App whose langfile to generate. @param langfile: Name of the langfile. Must follow the standard: ca_ES_ALL @return: Google OpenSocial compatible XML, or an HTTP error code if an error occurs. """ app = get_app(appid) if app is None: return render_template("composers/errors.html", message="Error 404: App doesn't exist."), 404 # The composer MUST be 'translate' if app.composer != "translate": return render_template("composers/errors.html", message="Error 500: The composer for the specified App is not a Translate composer."), 500 # Parse the appdata appdata = json.loads(app.data) bundles = appdata["bundles"] if langfile not in bundles: dbg_info = str(bundles.keys()) return render_template("composers/errors.html", message="Error 404: Could not find such language for the specified app. Available keys are: " + dbg_info), 404 bundle = Bundle.from_jsonable(bundles[langfile]) output_xml = bundle.to_xml() response = make_response(output_xml) response.mimetype = "application/xml" return response
def test_cannot_transfer_into_different_spec(self): """ If we try to transfer to an app with a different spec, it is refused. """ # Change the spec of the second app so that we can test. self.login("testuser2", "password") secondApp = get_app(self.secondApp) # Change the spec of the second app only. secondApp.spec = self.other_spec db.session.add(secondApp) db.session.commit() self.login("testuser", "password") url = u"/composers/translate/transfer_ownership?%s" % urllib.urlencode(dict( appid=self.firstApp, lang="all_ALL", transfer=self.secondApp )) rv = self.flask_app.post(url) print rv.status_code assert rv.status_code == 400 # Request denied
def change_appdescription(appid): """ Changes the app description. """ result = {} app = get_app(appid) if app is None: result["result"] = "error" result["message"] = "appid not provided" return jsonify(**result), 400 description = request.values.get("description") if description is None: result["result"] = "error" result["message"] = "new description not provided" return jsonify(**result), 400 app.description = description save_app(app) result["result"] = "success" result["message"] = "" return jsonify(**result)
def change_appname(appid): """ Changes the appname. """ result = {} app = get_app(appid) if app is None: result["result"] = "error" result["message"] = "appid not provided" return jsonify(**result), 400 name = request.values.get("name") if name is None: result["result"] = "error" result["message"] = "new name not provided" return jsonify(**result), 400 app.name = name save_app(app) result["result"] = "success" result["message"] = "" return jsonify(**result)
def save_data(self, app_id, data): """ Wrapper of the appstorage API. It saves the data in JSON format. """ app = appstorage.get_app(app_id) appstorage.update_app_data(app, data)
def translate_edit(): """ Translation editor for the selected language. @note: Returns error 400 if the source language or group don't exist. """ # No matter if we are handling a GET or POST, we require these parameters. appid = request.values.get("appid") srclang = request.values.get("srclang") targetlang = request.values.get("targetlang") srcgroup = request.values.get("srcgroup") targetgroup = request.values.get("targetgroup") # Retrieve the application we want to view or edit. app = get_app(appid) if app is None: return render_template("composers/errors.html", message=gettext("App not found")), 404 bm = BundleManager.create_from_existing_app(app.data) spec = bm.get_gadget_spec() # Retrieve the bundles for our lang. For this, we build the code from the info we have. srcbundle_code = BundleManager.partialcode_to_fullcode(srclang, srcgroup) targetbundle_code = BundleManager.partialcode_to_fullcode(targetlang, targetgroup) srcbundle = bm.get_bundle(srcbundle_code) # Ensure the existence of the source bundle. if srcbundle is None: return render_template("composers/errors.html", message=gettext("The source language and group combination does not exist")), 400 targetbundle = bm.get_bundle(targetbundle_code) # The target bundle doesn't exist yet. We need to create it ourselves. if targetbundle is None: splits = targetlang.split("_") if len(splits) == 2: lang, country = splits targetbundle = Bundle(lang, country, targetgroup) bm.add_bundle(targetbundle_code, targetbundle) # Get the owner for this target language. owner_app = _db_get_lang_owner_app(spec, targetlang) # If the language has no owner, we declare ourselves as owners. if owner_app is None: _db_declare_ownership(app, targetlang) owner_app = app # We override the standard Ownership's system is_owner. # TODO: Verify that this doesn't break anything. is_owner = owner_app == app # Get the language names target_translation_name = targetbundle.get_readable_name() source_translation_name = srcbundle.get_readable_name() # This is a GET request. We are essentially viewing-only. if request.method == "GET": pass # This is a POST request. We need to save the entries. else: # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message=gettext("Request does not seem to come from the right source (csrf check)")), 400 # Retrieve a list of all the key-values to save. That is, the parameters which start with _message_. messages = [(k[len("_message_"):], v) for (k, v) in request.values.items() if k.startswith("_message_")] # Save all the messages we retrieved from the POST or GET params into the Bundle. for identifier, msg in messages: if len(msg) > 0: # Avoid adding empty messages. targetbundle.add_msg(identifier, msg) # Now we need to save the changes into the database. json_str = bm.to_json() update_app_data(app, json_str) flash(gettext("Changes have been saved."), "success") propose_to_owner = request.values.get("proposeToOwner") if propose_to_owner is not None and owner_app != app: # Normally we will add the proposal to the queue. However, sometimes the owner wants to auto-accept # all proposals. We check for this. If the autoaccept mode is enabled on the app, we do the merge # right here and now. obm = BundleManager.create_from_existing_app(owner_app.data) if obm.get_autoaccept(): flash(gettext("Changes are being applied instantly because the owner has auto-accept enabled")) # Merge into the owner app. obm.merge_bundle(targetbundle_code, targetbundle) # Now we need to update the owner app's data. Because we aren't the owners, we can't use the appstorage # API directly. owner_app.data = obm.to_json() db.session.add(owner_app) db.session.commit() # [Context: We are not the leading Bundles, but our changes are merged directly into the leading Bundle] # We report the change to a "leading" bundle. on_leading_bundle_updated(spec, targetbundle) else: # We need to propose this Bundle to the owner. # Note: May be confusing: app.owner.login refers to the generic owner of the App, and not the owner # we are talking about in the specific Translate composer. proposal_data = {"from": app.owner.login, "timestamp": time.time(), "bundle_code": targetbundle_code, "bundle_contents": targetbundle.to_jsonable()} proposal_json = json.dumps(proposal_data) # Link the proposal with the Owner app. add_var(owner_app, "proposal", proposal_json) flash(gettext("Changes have been proposed to the owner")) # If we are the owner app. if owner_app == app: # [Context: We are the leading Bundle] # We report the change. on_leading_bundle_updated(spec, targetbundle) # Check whether the user wants to exit or to continue editing. if "save_exit" in request.values: return redirect(url_for("user.apps.index")) return render_template("composers/translate/edit.html", is_owner=is_owner, app=app, srcbundle=srcbundle, targetbundle=targetbundle, spec=spec, target_translation_name=target_translation_name, source_translation_name=source_translation_name)
def load_configuration(app_id): """ load_configuration(app_id) This function provides the configuration of a concept map. @param app_id: Identifier of the application. It will be unique within the list of user's apps. @return: The JSON response with the current values stored in the configuration (concepts, relations) """ # This block contains analogous checks implemented for the def wrapper(appid) method in the AdaptorPlugin class. # Responses are JSON-encoded data. if not app_id: error_message = { 'status': 400, 'message': 'App id not provided: ' + request.url, } response = make_response(json.dumps(error_message, indent=True)) response.mimetype = "application/json" response.status_code = 400 return response app = appstorage.get_app(app_id) if app is None: error_message = { 'status': 404, 'message': 'Concept map not found: ' + request.url, } response = make_response(json.dumps(error_message, indent=True)) response.mimetype = "application/json" response.status_code = 404 return response data = adaptor.load_data(app_id) if data['adaptor_type'] != 'concept_mapper': error_message = { 'status': 500, 'message': 'This is not a Concept Map: ' + request.url, } response = make_response(json.dumps(error_message, indent=True)) response.mimetype = "application/json" response.status_code = 500 return response else: data = adaptor.load_data(app_id) debug = data["debug"] actionlogging = data["actionlogging"] show_prompts = data["show_prompts"] textarea_concepts = data["textarea_concepts"] combobox_concepts = data["combobox_concepts"] drop_external = data["drop_external"] concepts = data["concepts"] relations = data["relations"] config_output = {'debug': debug, 'actionlogging': actionlogging, 'show_prompts': show_prompts, 'textarea_concepts': textarea_concepts, 'combobox_concepts': combobox_concepts, 'drop_external': drop_external, 'concepts': [concepts], 'relations': [relations]} response = make_response(json.dumps(config_output, indent=True)) response.mimetype = "application/json" return response
def translate_delete(): """ Handles the translate app delete endpoint. Only the user who owns the App can delete it. This is ensured at the appstorage level. A 401 code is returned if an attempt to delete other user's App is made. """ appid = request.values.get("appid") if not appid: return "appid not provided", 400 app = get_app(appid) if app is None: return "App not found", 404 # Get our spec. spec = db.session.query(AppVar.value).filter_by(app=app, name="spec").first()[0] # Find out which languages we own. ownerships = _db_get_app_ownerships(app) # Find out which apps we can transfer to. transfer_apps = _db_get_spec_apps(spec) transfer_apps = [a for a in transfer_apps if a != app] # If GET we display the confirmation screen and do not actually delete it. if request.method == "GET": return render_template("composers/translate/delete.html", app=app, ownerships=ownerships, transfer_apps=transfer_apps) # If POST we consider whether the user clicked Delete or Cancel in the confirmation screen. elif request.method == "POST": # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message=gettext("Request does not seem to come from the right source (csrf check)")), 400 # If the user didn't click delete he probably clicked cancel. # We return to the Apps View page. if not "delete" in request.form: return redirect(url_for("user.apps.index")) try: # If we have ownerships and we have someone to transfer them to, # then we need to do it. if len(ownerships) > 0 and len(transfer_apps) > 0: transfer_app_id = request.values.get("transfer") if transfer_app_id is None: return render_template("composers/errors.html", message=gettext("transfer parameter missing")), 400 transfer_app = get_app(transfer_app_id) if transfer_app is None: return render_template("composers/errors.html", message=gettext("could not retrieve app")), 400 # Transfer all ownerships to the selected app. for o in ownerships: o.app = transfer_app db.session.add(o) db.session.commit() transfer_app = get_app(transfer_app_id) delete_app(app) flash(gettext("App successfully deleted."), "success") except NotAuthorizedException: return render_template("composers/errors.html", message=gettext("Not Authorized")), 401 return redirect(url_for("user.apps.index"))
def load_data(self, appid): """ Wrapper of the appstorage API. It returns a valid JSON file. """ app = appstorage.get_app(appid) return json.loads(app.data)
def get_name(self, appid): app = appstorage.get_app(appid) return app.name
def translate_proposed_list(): """ Displays the list of proposed translations. """ # No matter if we are handling a GET or POST, we require these parameters. appid = request.values["appid"] app = get_app(appid) appdata = json.loads(app.data) # TODO: !!!!! THIS IS MISSING AND IS IMPORTANT !!!!! # Ensure that only the app owner can carry out these operations. # owner_app = _db_get_owner_app(appdata["spec"]) # if app != owner_app: # return render_template("composers/errors.html", # message="Not Authorized: You don't seem to be the owner of this app") # Get the list of proposed translations. proposal_vars = _db_get_proposals(app) proposed_translations = [] for prop in proposal_vars: propdata = json.loads(prop.value) propdata["id"] = str(prop.var_id) proposed_translations.append(propdata) # If we received a POST with acceptButton set then we will need to merge the # proposal. if request.method == "POST" and request.values.get("acceptButton") is not None: proposal_id = request.values.get("proposals") if proposal_id is None: return render_template("composers/errors.html", message=gettext("Proposal not selected")) merge_data = request.values.get("data") if merge_data is None: return render_template("composers/errors.html", message=gettext("Merge data was not provided")) merge_data = json.loads(merge_data) # TODO: Optimize this. We already have the vars. proposal = AppVar.query.filter_by(app=app, var_id=proposal_id).first() if proposal is None: return render_template("composers/errors.html", message=gettext("Proposals not found")) data = json.loads(proposal.value) bundle_code = data["bundle_code"] proposed_bundle = Bundle.from_messages(merge_data, bundle_code) bm = BundleManager.create_from_existing_app(app.data) bm.merge_bundle(bundle_code, proposed_bundle) update_app_data(app, bm.to_json()) flash(gettext("Merge done."), "success") # Remove the proposal from the DB. remove_var(proposal) # Remove it from our current proposal list as well, so that it isn't displayed anymore. proposed_translations = [prop for prop in proposed_translations if prop["id"] != proposal_id] # The DENY button was pressed. We have to discard the whole proposal. elif request.method == "POST" and request.values.get("denyButton") is not None: proposal_id = request.values.get("proposals") if proposal_id is None: return render_template("composers/errors.html", message=gettext("Proposal not selected")) proposal = AppVar.query.filter_by(app=app, var_id=proposal_id).first() if proposal is None: return render_template("composers/errors.html", message=gettext("Proposal not found")) remove_var(proposal) # Remove it from our current proposal list as well, so that it isn't displayed anymore. proposed_translations = [prop for prop in proposed_translations if prop["id"] != proposal_id] return render_template("composers/translate/proposed_list.html", app=app, proposals=proposed_translations)
def translate_selectlang(): """ Source language & target language selection. Different cases when creating an App: - Default translation exists; other translations exist -> Created normally - Default translation DOES NOT exist or is invalid; english translations exist or is invalid -> English is the default - Default translation DOES NOT exist or is invalid; english translation DOES NOT exist or is invalid; other translations exist -> First Other translation is the default - Default translation, english translation, and other translations DO NOT exist or are invalid -> NoValidTranslations page; App is not created """ # Note: The name pcode refers to the fact that the codes we deal with here are partial (do not include # the group). # We will build a list of possible languages using the babel library. languages = babel.core.Locale("en", "US").languages.items() languages.sort(key=lambda it: it[1]) # TODO: Currently, we filter languages which contain "_" in their code so as to simplify. # Because we use _ throughout the composer as a separator character, trouble is caused otherwise. # Eventually we should consider whether we need to support special languages with _ # on its code. targetlangs_codes = [lang[0] + "_ALL" for lang in languages if "_" not in lang[0]] targetlangs_list = [{"pcode": code, "repr": BundleManager.get_locale_english_name( *BundleManager.get_locale_info_from_code(code))} for code in targetlangs_codes] full_groups_list = [("ALL", "ALL"), ("10-13", "Preadolescence (age 10-13)"), ("14-18", "Adolescence (age 14-18)")] # As of now (may change in the future) if it is a POST we are creating the app for the first time. # Hence, we will need to carry out a full spec retrieval. if request.method == "POST": # Protect against CSRF attacks. if not verify_csrf(request): return render_template("composers/errors.html", message=gettext( "Request does not seem to come from the right source (csrf check)")), 400 # URL to the XML spec of the gadget. appurl = request.form.get("appurl") spec = appurl if appurl is None or len(appurl) == 0: flash(gettext("An application URL is required"), "error") return redirect(url_for("translate.translate_index")) base_appname = request.values.get("appname") if base_appname is None: return render_template("composers/errors.html", message=gettext("An appname was not specified")) try: # XXX FIXME # TODO: this makes this method to call twice the app_xml. We shouldn't need # that. We should have the contents here downloaded for later. if appurl.startswith(('http://', 'https://')): print appurl xmldoc = minidom.parseString(urllib2.urlopen(appurl).read()) appurl = get_original_url(xmldoc, appurl) print "New app xml:", appurl except: traceback.print_exc() pass # Generates a unique (for the current user) name for the App, # based on the base name that the user himself chose. Note that # this method can actually return None under certain conditions. appname = _find_unique_name_for_app(base_appname) if appname is None: return render_template("composers/errors.html", message=gettext("Too many Apps with the same name. Please, choose another.")) # Create a fully new App. It will be automatically generated from a XML. try: bm = BundleManager.create_new_app(appurl) except NoValidTranslationsException: return render_template("composers/errors.html", message=gettext( "The App you have chosen does not seem to have any translation. At least a base translation is required, which will often be" " prepared by the original developer.") ), 400 except InvalidXMLFileException: # TODO: As of now, not sure that this exception can actually ever arise. Maybe it should be merged with NoValidTranslationsException. return render_template("composers/errors.html", message=gettext( "Invalid XML in either the XML specification file or the XML translation bundles that it links to.")), 400 except MissingSchema: return render_template("composers/errors.html", message=gettext( "Failed to retrieve the XML spec. The URL was maybe invalid or not available.")), 400 if len(bm._bundles) == 0: # TODO: Consider adding a "go-back" screen / button. return render_template("composers/errors.html", message=gettext( "The App you have chosen does not seem to have a base translation. The original developer needs to prepare it for internationalization first.")), 400 spec = bm.get_gadget_spec() # For later # Build JSON data js = bm.to_json() # Create a new App from the specified XML app = create_app(appname, "translate", js) # Register our appurl as the "spec" in an app-specific variable in the DB. This will let us search later, for # certain advanced features. set_var(app, "spec", appurl) # Handle Ownership-related logic here. # Locate the owner for the App's DEFAULT language. ownerApp = _db_get_lang_owner_app(appurl, "all_ALL") # If there isn't already an owner for the default languages, we declare ourselves # as the owner for this App's default language. if ownerApp is None: _db_declare_ownership(app, "all_ALL") ownerApp = app # Report initial bundle creation. Needed for the MongoDB replica. for bundle in bm.get_bundles("all_ALL"): on_leading_bundle_updated(spec, bundle) # We do the same for the other included languages which are not owned already. # If the other languages have a translation but no owner, then we declare ourselves as their owner. for partialcode in bm.get_langs_list(): otherOwner = _db_get_lang_owner_app(appurl, partialcode) if otherOwner is None: _db_declare_ownership(app, partialcode) # Report initial bundle creation. Needed for the MongoDB replica. for bundle in bm.get_bundles(partialcode): on_leading_bundle_updated(spec, bundle) # Advanced merge. Merge owner languages into our bundles. do_languages_initial_merge(app, bm) # Find out which locales does the app provide (for now). translated_langs = bm.get_locales_list() # We do a redirect rather than rendering in the POST. This way we can get proper # URL. return redirect(url_for('translate.translate_selectlang', appid=app.unique_id)) # This was a GET, the app should exist already somehow, we will try to retrieve it. elif request.method == "GET": appid = request.args.get("appid") if appid is None: flash(gettext("appid not received"), "error") # An appid is required. return redirect(url_for("user.apps.index")) app = get_app(appid) if app is None: return render_template("composers/errors.html", message=gettext("Specified App doesn't exist")), 404 # Load a BundleManager from the app data. bm = BundleManager.create_from_existing_app(app.data) spec = bm.get_gadget_spec() # Check whether an owner exists. If it doesn't, it means the original owner was deleted. # In that case, we make ourselves a new owner. # Locate the owner for the App's DEFAULT language. ownerApp = _db_get_lang_owner_app(spec, "all_ALL") # If there isn't already an owner for the default languages, we declare ourselves # as the owner for this App's default language. # TODO: NOTE: With the latest changes, this should never happen, because owners are declared # for all existing languages by the user who creates the app. It may, however, happen in the future, # when we take deletions and such into account. if ownerApp is None: _db_declare_ownership(app, "all_ALL") ownerApp = app translated_langs = bm.get_locales_list() # The following is again common for both GET (view) and POST (edit). # Check ownership. Probably eventually we will remove the ownership check above. ownerApp = _db_get_lang_owner_app(spec, "all_ALL") if ownerApp == app: is_owner = True else: is_owner = False owner = ownerApp.owner if not is_owner and owner is None: # TODO: Improve this error handling. This should NEVER happen. flash(gettext("Error: Language Owner is None"), "error") return render_template("composers/errors.html", message=gettext("Internal Error: Language owner is None")), 500 # Just for the count of proposals proposal_num = len(_db_get_proposals(app)) # Build a dictionary. For each source lang, a list of source groups. src_groups_dict = defaultdict(list) for loc in translated_langs: src_groups_dict[loc["pcode"]].append(loc["group"]) locales_codes = [tlang["pcode"] for tlang in translated_langs] # Remove from the suggested targetlangs those langs which are already present on the bundle manager, # because those will be added to the targetlangs by default. suggested_target_langs = [elem for elem in targetlangs_list if elem["pcode"] not in locales_codes] # We pass the autoaccept data var so that it can be rendered. # TODO: Optimize this once we have this fully covered with tests. data = json.loads(app.data) autoaccept = data.get("autoaccept", True) # We autoaccept by default. Problems may arise if this value changes, because it is used in a couple of places. # We pass some parameters as JSON strings because they are generated dynamically # through JavaScript in the template. return render_template("composers/translate/selectlang.html", app=app, # Current app object. xmlspec=spec, # URL to the App XML. autoaccept=autoaccept, # Whether the app is configured to autoaccept proposals or not. suggested_target_langs=suggested_target_langs, # Suggested (not already translated) langs source_groups_json=json.dumps(src_groups_dict), # Source groups in a JSON string full_groups_json=json.dumps(full_groups_list), # (To find names etc) target_groups=full_groups_list, # Target groups in a JSON string translated_langs=translated_langs, # Already translated langs is_owner=is_owner, # Whether the loaded app has the "Owner" status owner=owner, # Reference to the Owner proposal_num=proposal_num) # Number of pending translation proposals