def test_get_ownerships(self): # There should be no ownerships declared on the spec. ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 0 # We now declare 1 ownership. _db_declare_ownership(self.tapp, "test_TEST") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # We now create a second app for further testing. app2 = api.create_app("UTApp2", "translate", "{'spec':'http://justatest.com'}") api.add_var(app2, "spec", "http://justatest.com") # Ensure we still have 1 ownership. ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # Add a second ownership for another language. _db_declare_ownership(app2, "testen_TESTEN") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 2 # Ensure that the ownerships are right. firstOwnership = next(o for o in ownerships if o.value == "test_TEST") assert firstOwnership.app == self.tapp secondOwnership = next(o for o in ownerships if o.value == "testen_TESTEN") assert secondOwnership.app == app2
def test_declare_ownership(self): _db_declare_ownership(self.tapp, "test_TEST") vars = api.get_all_vars(self.tapp) var = next(var for var in vars if var.name == "ownership") assert var.name == "ownership" assert var.value == "test_TEST"
def test_get_app_ownerships(self): """ Test the method to retrieve the ownerships given an app. """ _db_declare_ownership(self.tapp, "test_TEST") ownerships = _db_get_app_ownerships(self.tapp) assert len(ownerships) == 1
def test_conflicting_composer(self): """ Check that there is no mistake when there is a conflicting composer using the same appvar names. """ # We now declare 1 ownership. _db_declare_ownership(self.tapp, "test_TEST") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # We now create a non-translate app. app2 = api.create_app("UTApp2", "dummy", "http://justatest.com", "{'spec':'http://justatest.com'}") api.add_var(app2, "ownership", "test_TEST") # Make sure that even though we added an ownership on an app with the same spec, it won't be # taken into account because it is a DUMMY and not a TRANSLATE composer. assert len(ownerships) == 1
def test_transfer_ownership(self): """ Tests the method to transfer ownership. """ # We now declare 1 ownership. _db_declare_ownership(self.tapp, "test_TEST") ownerships = _db_get_ownerships("http://justatest.com") assert len(ownerships) == 1 # We now create a second app for further testing. app2 = api.create_app("UTApp2", "translate", "http://justatest.com", "{'spec':'http://justatest.com'}") # We transfer the ownership to the second app. _db_transfer_ownership("test_TEST", self.tapp, app2) # Verify that the ownership has indeed been transferred.. owner = _db_get_lang_owner_app("http://justatest.com", "test_TEST") assert owner == app2
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 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
def test_get_lang_owner_app(self): _db_declare_ownership(self.tapp, "test_TEST") owner_app = _db_get_lang_owner_app("http://justatest.com", "test_TEST") assert owner_app == self.tapp