def create(): form = ApplicationForm() if form.validate_on_submit(): form_scale = _get_scale_value(form) application = EmbedApplication(url=form.url.data, name=form.name.data, owner=current_golab_user(), height=form.height.data, scale=form_scale) db.session.add(application) try: db.session.commit() except Exception as e: traceback.print_exc() return render_template( "embed/error.html", message=gettext( "There was an error creating an application")), 500 else: return redirect(url_for('.edit', identifier=application.identifier)) return render_template("embed/create.html", form=form, header_message=gettext("Add a web"))
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 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'] return redirect(url_for(adaptor_plugin._edit_endpoint, appid=appid))
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 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'] return redirect(url_for(adaptor_plugin._edit_endpoint, appid=appid))
def find_definition_script(contents, url): data_config_definition_scripts = CONFIG_DEFINITION_REGEX.findall(contents) if len(data_config_definition_scripts) > 1: flash( gettext( "Too many scripts with data-configuration-definition found. This may happen if you have commented one. There can be a single one." )) elif len(data_config_definition_scripts) < 1: flash( gettext( "No script with data-configuration-definition found. Is the app adapted for the Go-Lab JavaScript configuration tools?" )) else: src_attributes = SRC_REGEX.findall(data_config_definition_scripts[0]) if len(src_attributes) > 1: flash( gettext( "In the data-configuration-definition tag, there must be a single src attribute" )) elif len(src_attributes) < 1: flash( gettext( "In the data-configuration-definition tag, there must be at least an src attribute. None found" )) else: src_attribute = src_attributes[0] return make_url_absolute(src_attribute, url)
def app_xml(identifier): application = db.session.query(EmbedApplication).filter_by( identifier=identifier).first() if application is None: return render_template( "embed/error.xml", message=gettext("Application '{identifier}' not found").format( identifier=identifier)), 404 apps_per_language = {} languages = ['en'] for translation in application.translations: apps_per_language[translation.language] = translation.url languages.append(translation.language) author = application.owner.display_name author_email = application.owner.email response = make_response( render_template( "embed/app.xml", author=author, identifier=identifier, author_email=author_email, app=application, languages=languages, apps_per_language=apps_per_language, title=gettext("Application {name}").format(name=application.name))) response.content_type = 'application/xml' return response
def adapt_type_selection(): """ adapt_type_selection() Loads the page that lets the user choose the adaptation type, and that lets the user view or duplicate an existing adaptation instead. This method DOES NOT REQUIRE LOGIN but will display a different view when not logged in. """ # Check if we are logged in. logged_in = current_user() is not None # If we are not logged in disallow POST. if not logged_in and request.method == "POST": return render_template("composers/errors.html", message=gettext("Cannot POST to this URL if not logged in")), 403 # We require the appurl parameter. appurl = request.values.get("appurl") if appurl is None: return render_template("composers/errors.html", message=gettext("appurl parameter not specified")) # Obtain a list of every adaptation that exists in the database for the specified appurl. # TODO: Move db_helpers somewhere else. Makes no sense to use translator files in the adaptor. apps_list = _db_get_spec_apps(appurl) apps = [] for app in apps_list: if app.composer != "adapt": continue apps.append({ "name": app.name, "desc": app.description, "owner": app.owner.name, "type": "adapt", # TO-DO: Specify the specific adaptor sub-type. "app_id": app.unique_id }) # We will only get here if we are logged in if request.method == "POST": # 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 adaptor_type = request.form["adaptor_type"] if adaptor_type and adaptor_type in ADAPTORS: # In order to show the list of apps we redirect to other url return redirect(url_for("adapt.adapt_create", adaptor_type=adaptor_type)) else: # An adaptor_type is required. flash("Invalid adaptor type", "error") if logged_in: return render_template("composers/adapt/type.html", adaptors=ADAPTORS, apps=apps) # Version for the public else: return render_template("composers/adapt/public_type.html", adaptors=ADAPTORS, apps=apps)
def obtain_groups(): """ Obtains the groups that are available for translation, as an Ordered Dictionary. :return: Ordered dictionary with the name of the groups identified by each key. :rtype: OrderedDict """ groups = OrderedDict() groups["ALL"] = "ALL" groups["10-13"] = gettext("Preadolescence (age 10-13)") groups["14-18"] = gettext("Adolescence (age 14-18)") return groups
def app(identifier): application = db.session.query(EmbedApplication).filter_by( identifier=identifier).first() if application is None: return render_template( "embed/error.html", message=gettext("Application '{identifier}' not found").format( identifier=identifier)), 404 return render_template( "embed/app.html", app=application, title=gettext("Application {name}").format(name=application.name))
def create(): form = ApplicationForm() if form.validate_on_submit(): form_scale = _get_scale_value(form) application = EmbedApplication(url = form.url.data, name = form.name.data, owner = current_golab_user(), height=form.height.data, scale=form_scale) db.session.add(application) try: db.session.commit() except Exception as e: traceback.print_exc() return render_template("embed/error.html", message = gettext("There was an error creating an application")), 500 else: return redirect(url_for('.edit', identifier=application.identifier)) return render_template("embed/create.html", form=form, header_message=gettext("Add a web"))
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 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 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 find_definition_script(contents, url): data_config_definition_scripts = CONFIG_DEFINITION_REGEX.findall(contents) if len(data_config_definition_scripts) > 1: flash(gettext( "Too many scripts with data-configuration-definition found. This may happen if you have commented one. There can be a single one.")) elif len(data_config_definition_scripts) < 1: flash(gettext( "No script with data-configuration-definition found. Is the app adapted for the Go-Lab JavaScript configuration tools?")) else: src_attributes = SRC_REGEX.findall(data_config_definition_scripts[0]) if len(src_attributes) > 1: flash(gettext("In the data-configuration-definition tag, there must be a single src attribute")) elif len(src_attributes) < 1: flash(gettext( "In the data-configuration-definition tag, there must be at least an src attribute. None found")) else: src_attribute = src_attributes[0] return make_url_absolute(src_attribute, url)
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 new(): # If we receive a get we just want to show the page. if request.method == "GET": return render_template("composers/dummy/new.html") # If we receive a post we have filled the page and are creating the app. elif request.method == "POST": name = request.form["name"] try: app = appstorage.create_app(name, "dummy", data='{"text":""}') except appstorage.AppExistsException: flash(gettext("An App with that name exists already"), "error") return render_template("composers/dummy/new.html", name=name) return redirect(url_for("dummy.edit", appid=app.unique_id))
def app_xml(identifier): application = db.session.query(EmbedApplication).filter_by(identifier = identifier).first() if application is None: return render_template("embed/error.xml", message = gettext("Application '{identifier}' not found").format(identifier=identifier)), 404 apps_per_language = {} languages = ['en'] for translation in application.translations: apps_per_language[translation.language] = translation.url languages.append(translation.language) author = application.owner.display_name author_email = application.owner.email response = make_response(render_template("embed/app.xml", author = author, identifier=identifier, author_email = author_email, app = application, languages=languages, apps_per_language = apps_per_language, title = gettext("Application {name}").format(name=application.name))) response.content_type = 'application/xml' return response
def new(): # If we receive a get we just want to show the page. if request.method == "GET": return render_template("composers/dummy/new.html") # If we receive a post we have filled the page and are creating the app. elif request.method == "POST": name = request.form["name"] try: app = appstorage.create_app(name, "dummy", spec_url=None, data='{"text":""}') except appstorage.AppExistsException: flash(gettext("An App with that name exists already"), "error") return render_template("composers/dummy/new.html", name=name) return redirect(url_for("dummy.edit", appid=app.unique_id))
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 apps(): applications = db.session.query(EmbedApplication).order_by(EmbedApplication.last_update).all() return render_template("embed/apps.html", applications = applications, title = gettext("List of applications"))
def edit(app_id): # Load data from the database for this application data = adaptor.load_data(app_id) new_url = False contents = None url_form = UrlForm() if not url_form.url.data: url_form.url.data = data['url'] if request.method == 'POST': value = request.form['url'] if value != data['url']: if url_form.validate_on_submit(): try: contents = urllib2.urlopen(value).read() except: if not url_form.url.errors: url_form.url.errors = [] url_form.url.errors.append( gettext("Could not download the provided URL")) else: try: minidom.parseString(contents) except: if not url_form.url.errors: url_form.url.errors = [] url_form.url.errors.append( gettext("The provided URL is not a valid XML!")) else: # Reset the configuration data['url'] = value new_url = True data['configuration'] = None data['configuration_name'] = None data["initialised"] = True adaptor.save_data(app_id, data) url = data['url'] definition_script = None if url and url.startswith(('http://', 'https://')): if not contents: try: contents = urllib2.urlopen(url).read() except: flash(gettext("Could not download the provided URL")) if contents: definition_script = find_definition_script(contents, url) external_url = url_for('.app_xml', app_id=app_id, _external=True) preview_url = shindig_url("/gadgets/ifr?nocache=1&url=%s" % external_url) if new_url: try: # Force shindig to refresh the ifr. We had problems with new URLs urllib2.urlopen(preview_url).read() except: pass configuration_name = data['configuration_name'] configuration = json.dumps(data['configuration'], indent=4) return render_template( "jsconfig/edit.html", initialised=data.get("initialised"), # Indicate whether the first URL was successfully loaded, so that we can prevent further URL changes url=url or '', definition_script=definition_script, app_id=app_id, preview_url=preview_url, configuration_name=configuration_name, configuration=configuration, url_form=url_form, name=adaptor.get_name(app_id))
def edit(app_id): # Load data from the database for this application data = adaptor.load_data(app_id) new_url = False contents = None url_form = UrlForm() if not url_form.url.data: url_form.url.data = data['url'] if request.method == 'POST': value = request.form['url'] if value != data['url']: if url_form.validate_on_submit(): try: contents = urllib2.urlopen(value).read() except: if not url_form.url.errors: url_form.url.errors = [] url_form.url.errors.append(gettext("Could not download the provided URL")) else: try: minidom.parseString(contents) except: if not url_form.url.errors: url_form.url.errors = [] url_form.url.errors.append(gettext("The provided URL is not a valid XML!")) else: # Reset the configuration data['url'] = value new_url = True data['configuration'] = None data['configuration_name'] = None data["initialised"] = True adaptor.save_data(app_id, data) url = data['url'] definition_script = None if url and url.startswith(('http://', 'https://')): if not contents: try: contents = urllib2.urlopen(url).read() except: flash(gettext("Could not download the provided URL")) if contents: definition_script = find_definition_script(contents, url) external_url = url_for('.app_xml', app_id=app_id, _external=True) preview_url = shindig_url("/gadgets/ifr?nocache=1&url=%s" % external_url) if new_url: try: # Force shindig to refresh the ifr. We had problems with new URLs urllib2.urlopen(preview_url).read() except: pass configuration_name = data['configuration_name'] configuration = json.dumps(data['configuration'], indent=4) return render_template("jsconfig/edit.html", initialised=data.get("initialised"), # Indicate whether the first URL was successfully loaded, so that we can prevent further URL changes url=url or '', definition_script=definition_script, app_id=app_id, preview_url=preview_url, configuration_name=configuration_name, configuration=configuration, url_form=url_form, name=adaptor.get_name(app_id))
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 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 edit(identifier): existing_languages = { # lang: { # 'code': 'es', # 'name': 'Spanish', # 'url': 'http://....' # } } existing_languages_db = { # lang: db_instance } all_languages = list_of_languages() # Obtain from the database application = db.session.query(EmbedApplication).filter_by(identifier = identifier).first() if application is None: return "Application does not exist", 404 for translation in application.translations: existing_languages_db[translation.language] = translation existing_languages[translation.language] = { 'code': translation.language, 'name': all_languages.get(translation.language) or 'Language not supported anymore', 'url': translation.url } # languages added by the UI posted_languages = { # 'es' : 'http://.../' } if request.method == 'POST': for key in request.form: if key.startswith('language.'): lang_code = key[len('language.'):] if lang_code in all_languages: posted_languages[lang_code] = request.form[key] form = ApplicationForm(obj=application) if form.validate_on_submit(): # Check for new ones or changed for posted_language, url in posted_languages.items(): if posted_language in existing_languages_db: translation = existing_languages_db[posted_language] if translation.url != url: # Don't trigger unnecessary UPDATEs translation.url = url else: translation = EmbedApplicationTranslation(embed_application = application, url=url, language=posted_language) db.session.add(translation) # Delete old ones for existing_language, translation in existing_languages_db.items(): if existing_language not in posted_languages: existing_languages.pop(existing_language) db.session.delete(translation) form_scale = _get_scale_value(form) print(form_scale) application.update(url=form.url.data, name=form.name.data, height=form.height.data, scale=form_scale) db.session.commit() # Add the posted languages to the existing ones for lang_code, url in posted_languages.items(): existing_languages[lang_code] = { 'code' : lang_code, 'name' : all_languages[lang_code], 'url' : url } # Obtain the languages formatted as required but excluding those already added languages = obtain_formatted_languages(existing_languages) return render_template("embed/create.html", form=form, identifier=identifier, header_message=gettext("Edit web"), languages=languages, existing_languages=list(existing_languages.values()), all_languages=all_languages)
def apps(): applications = db.session.query(EmbedApplication).order_by( EmbedApplication.last_update).all() return render_template("embed/apps.html", applications=applications, title=gettext("List of applications"))
def edit(identifier): existing_languages = { # lang: { # 'code': 'es', # 'name': 'Spanish', # 'url': 'http://....' # } } existing_languages_db = { # lang: db_instance } all_languages = list_of_languages() # Obtain from the database application = db.session.query(EmbedApplication).filter_by( identifier=identifier).first() if application is None: return "Application does not exist", 404 for translation in application.translations: existing_languages_db[translation.language] = translation existing_languages[translation.language] = { 'code': translation.language, 'name': all_languages.get(translation.language) or 'Language not supported anymore', 'url': translation.url } # languages added by the UI posted_languages = { # 'es' : 'http://.../' } if request.method == 'POST': for key in request.form: if key.startswith('language.'): lang_code = key[len('language.'):] if lang_code in all_languages: posted_languages[lang_code] = request.form[key] form = ApplicationForm(obj=application) if form.validate_on_submit(): # Check for new ones or changed for posted_language, url in posted_languages.items(): if posted_language in existing_languages_db: translation = existing_languages_db[posted_language] if translation.url != url: # Don't trigger unnecessary UPDATEs translation.url = url else: translation = EmbedApplicationTranslation( embed_application=application, url=url, language=posted_language) db.session.add(translation) # Delete old ones for existing_language, translation in existing_languages_db.items(): if existing_language not in posted_languages: existing_languages.pop(existing_language) db.session.delete(translation) form_scale = _get_scale_value(form) print(form_scale) application.update(url=form.url.data, name=form.name.data, height=form.height.data, scale=form_scale) db.session.commit() # Add the posted languages to the existing ones for lang_code, url in posted_languages.items(): existing_languages[lang_code] = { 'code': lang_code, 'name': all_languages[lang_code], 'url': url } # Obtain the languages formatted as required but excluding those already added languages = obtain_formatted_languages(existing_languages) return render_template("embed/create.html", form=form, identifier=identifier, header_message=gettext("Edit web"), languages=languages, existing_languages=list( existing_languages.values()), all_languages=all_languages)
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 adapt_type_selection(): """ adapt_type_selection() Loads the page that lets the user choose the adaptation type, and that lets the user view or duplicate an existing adaptation instead. This method DOES NOT REQUIRE LOGIN but will display a different view when not logged in. """ # Check if we are logged in. logged_in = current_user() is not None # If we are not logged in disallow POST. if not logged_in and request.method == "POST": return render_template( "composers/errors.html", message=gettext("Cannot POST to this URL if not logged in")), 403 # We require the appurl parameter. appurl = request.values.get("appurl") if appurl is None: return render_template( "composers/errors.html", message=gettext("appurl parameter not specified")) # Obtain a list of every adaptation that exists in the database for the specified appurl. # TODO: Move db_helpers somewhere else. Makes no sense to use translator files in the adaptor. apps_list = _db_get_spec_apps(appurl) apps = [] for app in apps_list: if app.composer != "adapt": continue apps.append({ "name": app.name, "desc": app.description, "owner": app.owner.name, "type": "adapt", # TO-DO: Specify the specific adaptor sub-type. "app_id": app.unique_id }) # We will only get here if we are logged in if request.method == "POST": # 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 adaptor_type = request.form["adaptor_type"] if adaptor_type and adaptor_type in ADAPTORS: # In order to show the list of apps we redirect to other url return redirect( url_for("adapt.adapt_create", adaptor_type=adaptor_type)) else: # An adaptor_type is required. flash("Invalid adaptor type", "error") if logged_in: return render_template("composers/adapt/type.html", adaptors=ADAPTORS, apps=apps) # Version for the public else: return render_template("composers/adapt/public_type.html", adaptors=ADAPTORS, apps=apps)
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 app(identifier): application = db.session.query(EmbedApplication).filter_by(identifier = identifier).first() if application is None: return render_template("embed/error.html", message = gettext("Application '{identifier}' not found").format(identifier=identifier)), 404 return render_template("embed/app.html", app = application, title = gettext("Application {name}").format(name=application.name))