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 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)
示例#3
0
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)
示例#4
0
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)
示例#5
0
def adapt_type_selection():
    """
    adapt_type_selection()
    Loads the main page with the selection of adaptor apps (concept map, hypothesis or experiment design).
    @return: The adaptor type that the user has selected.
    """
    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")
    return render_template("composers/adapt/type.html", adaptors=ADAPTORS)
示例#6
0
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)
示例#7
0
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"))
示例#8
0
def adapt_create(adaptor_type):
    """
    adapt_create()
    Loads the form for creating new adaptor apps and the list of adaptor apps from a specific type.
    @return: The app unique id.
    """

    def build_edit_link(app):
        return url_for("adapt.adapt_edit", appid=app.unique_id)


    if adaptor_type not in ADAPTORS:
        flash("Invalid adaptor type", "error")
        return render_template('composers/adapt/create.html', apps=[], adaptor_type=adaptor_type,
                               build_edit_link=build_edit_link)

    app_plugin = ADAPTORS[adaptor_type]

    apps = appstorage.get_my_apps(adaptor_type=adaptor_type)

    # If a get request is received, we just show the new app form and the list of adaptor apps
    if request.method == "GET":
        return render_template('composers/adapt/create.html', apps=apps, adaptor_type=adaptor_type,
                               build_edit_link=build_edit_link)


    # If a post is received, we are creating an adaptor app.
    elif 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

        # We read the app details provided by the user
        name = request.form["app_name"]
        app_description = request.form["app_description"]

        if not name:
            flash("An application name is required", "error")
            return render_template("composers/adapt/create.html", name=name, apps=apps, adaptor_type=adaptor_type,
                                   build_edit_link=build_edit_link)

        if not app_description:
            app_description = ""

        # Build the basic JSON schema of the adaptor app
        data = {
            'adaptor_version': '1',
            'name': unicode(name),
            'description': unicode(app_description),
            'adaptor_type': unicode(adaptor_type)
        }

        # Fill with the initial structure
        data.update(app_plugin['initial'])

        # URL was originally added in a later stage in case it could be changed, but not anymore.
        # TODO: The app_plugin["initial"] seems to tend to contain an url field set to NULL. This is why we
        # initialize the URL here instead of the first data initialization. This should be tidied up to be
        # less confusing.
        appurl = unicode(request.values.get("appurl"))
        data['url'] = appurl

        #Dump the contents of the previous block and check if an app with the same name exists.
        # (TODO): do we force different names even if the apps belong to another adaptor type?
        app_data = json.dumps(data)

        print "INITIAL DATA: " + app_data

        try:
            # This is where the App object itself is created.
            app = appstorage.create_app(name, 'adapt', appurl, app_data, description=app_description)
            appstorage.add_var(app, 'adaptor_type', unicode(adaptor_type))
        except appstorage.AppExistsException:
            flash("An App with that name already exists", "error")
            return render_template("composers/adapt/create.html", name=name, apps=apps, adaptor_type=adaptor_type,
                                   build_edit_link=build_edit_link)

        return redirect(url_for("adapt.adapt_edit", appid=app.unique_id))
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)
示例#10
0
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