Ejemplo n.º 1
0
def transfer_ownership():

    # No matter if we are handling a GET or POST, we require these parameters.
    appid = request.values.get("appid")

    # Retrieve the application we want to view or edit.
    app = get_app(appid)
    if app is None:
        return render_template("composers/errors.html", message=gettext("App not found")), 404

    # Make sure the logged in user owns the app.
    user = current_user()
    if app.owner != user:
        return render_template("composers/errors.html", message=gettext("Not Authorized: User does not own app")), 403

    # Get the XMLSPEC
    bm = BundleManager.create_from_existing_app(app.data)
    spec = bm.get_gadget_spec()

    # Get the language
    lang = request.values.get("lang")
    if lang is None:
        return render_template("composers/errors.html", message=gettext("Lang not specified")), 400

    # Verify that we are the owners of the language we are trying to transfer.
    owner_app = _db_get_lang_owner_app(spec, lang)
    if owner_app != app:
        return render_template("composers/errors.html", message=gettext("Not Authorized: App does not own language")), 403

    # Get the possible apps to which we can concede ownership.
    apps = _db_get_spec_apps(spec)
    apps = [a for a in apps if a != app]

    # We received a POST request. We need to transfer the ownership.
    if request.method == "POST":

        # Protect against CSRF attacks.
        if not verify_csrf(request):
            return render_template("composers/errors.html",
                                   message=gettext("Request does not seem to come from the right source (csrf check)")), 400

        # Verify that we were passed the target app.
        targetapp = get_app(request.values.get("transfer"))
        if targetapp is None:
            return render_template("composers/errors.html", message=gettext("Target app not specified")), 400

        # Verify that the target app is of the same spec.
        targetappdata = json.loads(targetapp.data)
        targetspec = targetappdata["spec"]
        if targetspec != spec:
            return render_template("composers/errors.html", message=gettext("Target app does not have the same spec")), 400

        # Carry out the transfer.
        _db_transfer_ownership(lang, app, targetapp)

        # Redirect to selectlang.
        return redirect(url_for("translate.translate_selectlang", appid=app.unique_id))

    # For GET
    return render_template("composers/translate/transfer_ownership.html", app=app, apps=apps, xmlspec=spec, lang=lang)
    def test_autoaccept_post(self):

        # Check for 0 POST
        url = u"/composers/translate/config/autoaccept/" + self.tapp.unique_id
        rv = self.flask_app.post(url, data={"value": 0})
        assert rv.status_code == 200
        js = json.loads(rv.data)
        assert js["result"] == "success"
        assert js["value"] == False
        app = get_app(self.tapp.unique_id)
        appdata = json.loads(app.data)
        assert appdata["autoaccept"] == False

        # Check for 1 POST
        url = u"/composers/translate/config/autoaccept/" + self.tapp.unique_id
        rv = self.flask_app.post(url, data={"value": 1})
        assert rv.status_code == 200
        js = json.loads(rv.data)
        assert js["result"] == "success"
        assert js["value"] == True
        app = get_app(self.tapp.unique_id)
        appdata = json.loads(app.data)
        assert appdata["autoaccept"] == True

        # Check for INVALID POST
        url = u"/composers/translate/config/autoaccept/" + self.tapp.unique_id
        rv = self.flask_app.post(url, data={"value": 234124})  # Invalid value
        assert rv.status_code == 200
        js = json.loads(rv.data)
        assert js["result"] == "error"
Ejemplo n.º 3
0
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"))
Ejemplo n.º 4
0
def app_xml(appid, group):
    """
    app_xml(appid, group)

    Provided for end-users. This is the function that provides hosting for the
    gadget specs for a specified App. The gadget specs are actually dynamically
    generated, as every time a request is made the original XML is obtained and
    modified.

    @param appid: Identifier of the App.
    @param group: Group that will act as a filter. If, for instance, it is set to 14-18, then only
    Bundles that belong to that group will be shown.
    @return: XML of the modified Gadget Spec with the Locales injected, or an HTTP error code
    if an error occurs.
    """
    app = get_app(appid)

    if app is None:
        return render_template("composers/errors.html", message="Error 404: App doesn't exist"), 404

    # The composer MUST be 'translate'
    if app.composer != "translate":
        return render_template("composers/errors.html",
                               message="Error 500: The composer for the specified App is not Translate"), 500

    bm = BundleManager.create_from_existing_app(app.data)
    output_xml = bm.do_render_app_xml(appid, group)

    response = make_response(output_xml)
    response.mimetype = "application/xml"
    return response
    def _cleanup(self):
        """
        Does cleanup tasks in case the tests failed before.
        Can be invoked *before* and *after* the tests.
        """
        self.flask_app.get("/")  # This is required to create a context. Otherwise session etc don't exist.

        if self.firstApp is not None:
            app = api.get_app(self.firstApp.unique_id)
            if app is not None:
                api.delete_app(app)

        if self.secondApp is not None:
            app = api.get_app(self.secondApp.unique_id)
            if app is not None:
                api.delete_app(app)
Ejemplo n.º 6
0
def adapt_edit(appid):
    """
    adapt_edit()
    Form-based user interface for editing the contents of an adaptor app.
    @return: The final app with all its fields stored in the database.
    """
    if not appid:
        return "appid not provided", 400

    # TODO: Improve this: do not load the whole thing. We just need the variables.
    app = appstorage.get_app(appid)
    if app is None:
        return "Error: App not found", 500

    adaptor_types = [var for var in app.appvars if var.name == 'adaptor_type']
    if not adaptor_types:
        return "Error: no attached adaptor_type variable"
    adaptor_type = adaptor_types[0].value

    if adaptor_type not in ADAPTORS:
        return "Error: adaptor %s not currently supported" % adaptor_type

    adaptor_plugin = ADAPTORS[adaptor_type]['adaptor']

    return redirect(url_for(adaptor_plugin._edit_endpoint, appid=appid))
Ejemplo n.º 7
0
    def test_create_app(self):
        app = api.create_app("UTApp", "dummy", None, "{}")
        assert app is not None
        assert app.name == "UTApp"

        id = app.unique_id  # TODO: Probably no point for App to have two different unique ids.
        app = None
        app = api.get_app(id)
        assert app is not None
        assert app.name == "UTApp"
        assert app.owner == current_user()
Ejemplo n.º 8
0
    def test_create_app(self):
        app = api.create_app("UTApp", "dummy", "{}")
        assert app is not None
        assert app.name == "UTApp"

        id = app.unique_id  # TODO: Probably no point for App to have two different unique ids.
        app = None
        app = api.get_app(id)
        assert app is not None
        assert app.name == "UTApp"
        assert app.owner == current_user()
    def _cleanup(self):
        """
        Does cleanup tasks in case the tests failed before.
        Can be invoked *before* and *after* the tests.
        """
        self.flask_app.get("/")  # This is required to create a context. Otherwise session etc don't exist.

        self.login("testuser", "password")
        if self.firstApp is not None:
            app = api.get_app(self.firstApp)
            if app is not None:
                api.delete_app(app)

        self.login("testuser2", "password")
        if self.secondApp is not None:
            app = api.get_app(self.secondApp)
            if app is not None:
                api.delete_app(app)

        db.session.query(Spec).filter_by(url="TESTURL").delete()
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
0
    def test_change_description(self):
        """
        Test that changing the description of the app works.
        """

        post_url = u"/change/appdescription/" + self.tapp.unique_id
        rv = self.flask_app.post(post_url, data={"description": "A new App description"})

        assert rv.status_code == 200
        js = json.loads(rv.data)
        assert js["result"] == "success"

        # Ensure that the App Name changed.
        tapp = api.get_app(self.tapp.unique_id)
        assert tapp.description == "A new App description"
Ejemplo n.º 12
0
    def test_change_name(self):
        """
        Test that changing the name of the app works.
        """

        post_url = u"/change/appname/" + self.tapp.unique_id
        rv = self.flask_app.post(post_url, data={"name": "RenamedApp"})

        assert rv.status_code == 200
        js = json.loads(rv.data)
        assert js["result"] == "success"

        # Ensure that the App Description changed.
        tapp = api.get_app(self.tapp.unique_id)
        assert tapp.name == "RenamedApp"
Ejemplo n.º 13
0
        def wrapper(appid):
            # TODO: Improve this: do not load the whole thing. We just need the ID and so on.
            if not appid:
                return "appid not provided", 400
            app = appstorage.get_app(appid)
            if app is None:
                return "App not found", 500

            data = json.loads(app.data)
            adaptor_type = data["adaptor_type"]

            if adaptor_type != self.name:
                return "This Adaptor is not of this adaptor type", 400

            return func(appid)
Ejemplo n.º 14
0
        def wrapper(appid):
            # TODO: Improve this: do not load the whole thing. We just need the ID and so on.
            if not appid:
                return "appid not provided", 400
            app = appstorage.get_app(appid)
            if app is None:
                return "App not found", 500

            data = json.loads(app.data)
            adaptor_type = data["adaptor_type"]

            if adaptor_type != self.name:
                return "This Adaptor is not of this adaptor type", 400

            return func(appid)
Ejemplo n.º 15
0
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)
Ejemplo n.º 16
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)
Ejemplo n.º 17
0
def app_langfile(appid, langfile):
    """
    app_langfile(appid, langfile, age)

    Provided for end-users. This is the function that provides hosting for the
    langfiles for a specified App. The langfiles are actually dynamically
    generated (the information is extracted from the Translate-specific information).

    @param appid: Appid of the App whose langfile to generate.
    @param langfile: Name of the langfile. Must follow the standard: ca_ES_ALL
    @return: Google OpenSocial compatible XML, or an HTTP error code
    if an error occurs.
    """
    app = get_app(appid)

    if app is None:
        return render_template("composers/errors.html", message="Error 404: App doesn't exist."), 404

    # The composer MUST be 'translate'
    if app.composer != "translate":
        return render_template("composers/errors.html",
                               message="Error 500: The composer for the specified App is not a Translate composer."), 500

    # Parse the appdata
    appdata = json.loads(app.data)

    bundles = appdata["bundles"]
    if langfile not in bundles:
        dbg_info = str(bundles.keys())
        return render_template("composers/errors.html",
                               message="Error 404: Could not find such language for the specified app. Available keys are: " + dbg_info), 404

    bundle = Bundle.from_jsonable(bundles[langfile])

    output_xml = bundle.to_xml()

    response = make_response(output_xml)
    response.mimetype = "application/xml"
    return response
    def test_cannot_transfer_into_different_spec(self):
        """
        If we try to transfer to an app with a different spec, it is refused.
        """
        # Change the spec of the second app so that we can test.
        self.login("testuser2", "password")
        secondApp = get_app(self.secondApp)

        # Change the spec of the second app only.
        secondApp.spec = self.other_spec

        db.session.add(secondApp)
        db.session.commit()


        self.login("testuser", "password")
        url = u"/composers/translate/transfer_ownership?%s" % urllib.urlencode(dict(
            appid=self.firstApp,
            lang="all_ALL",
            transfer=self.secondApp
            ))
        rv = self.flask_app.post(url)
        print rv.status_code
        assert rv.status_code == 400 # Request denied
Ejemplo n.º 19
0
def change_appdescription(appid):
    """
    Changes the app description.
    """
    result = {}

    app = get_app(appid)
    if app is None:
        result["result"] = "error"
        result["message"] = "appid not provided"
        return jsonify(**result), 400

    description = request.values.get("description")
    if description is None:
        result["result"] = "error"
        result["message"] = "new description not provided"
        return jsonify(**result), 400

    app.description = description
    save_app(app)

    result["result"] = "success"
    result["message"] = ""
    return jsonify(**result)
Ejemplo n.º 20
0
def change_appname(appid):
    """
    Changes the appname.
    """
    result = {}

    app = get_app(appid)
    if app is None:
        result["result"] = "error"
        result["message"] = "appid not provided"
        return jsonify(**result), 400

    name = request.values.get("name")
    if name is None:
        result["result"] = "error"
        result["message"] = "new name not provided"
        return jsonify(**result), 400

    app.name = name
    save_app(app)

    result["result"] = "success"
    result["message"] = ""
    return jsonify(**result)
Ejemplo n.º 21
0
 def save_data(self, app_id, data):
     """ Wrapper of the appstorage API. It saves the data in JSON format. """
     app = appstorage.get_app(app_id)
     appstorage.update_app_data(app, data)
Ejemplo n.º 22
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)
Ejemplo n.º 23
0
def load_configuration(app_id):
    """
    load_configuration(app_id)
    This function provides the configuration of a concept map.

    @param app_id: Identifier of the application. It will be unique within the list of user's apps.
    @return: The JSON response with the current values stored in the configuration (concepts, relations)
    """

    # This block contains analogous checks implemented for the def wrapper(appid) method in the AdaptorPlugin class.
    # Responses are JSON-encoded data.
    if not app_id:
        error_message = {
            'status': 400,
            'message': 'App id not provided: ' + request.url,
        }
        response = make_response(json.dumps(error_message, indent=True))
        response.mimetype = "application/json" 
        response.status_code = 400
        return response

    app = appstorage.get_app(app_id)
    if app is None:
        error_message = {
            'status': 404,
            'message': 'Concept map not found: ' + request.url,
        }
        response = make_response(json.dumps(error_message, indent=True))
        response.mimetype = "application/json"        
        response.status_code = 404
        return response

    data = adaptor.load_data(app_id)
    if data['adaptor_type'] != 'concept_mapper':
        error_message = {
            'status': 500,
            'message': 'This is not a Concept Map: ' + request.url,
        }
        response = make_response(json.dumps(error_message, indent=True))
        response.mimetype = "application/json" 
        response.status_code = 500
        return response
    else:
        data = adaptor.load_data(app_id)

        debug = data["debug"]
        actionlogging = data["actionlogging"]
        show_prompts = data["show_prompts"]
        textarea_concepts = data["textarea_concepts"]
        combobox_concepts = data["combobox_concepts"]
        drop_external = data["drop_external"]
        concepts = data["concepts"]
        relations = data["relations"]
        
        config_output = {'debug': debug, 'actionlogging': actionlogging, 'show_prompts': show_prompts,
                                    'textarea_concepts': textarea_concepts, 'combobox_concepts': combobox_concepts, 'drop_external': drop_external,
                                    'concepts': [concepts], 'relations': [relations]}

        response = make_response(json.dumps(config_output, indent=True))
        response.mimetype = "application/json"        
        return response
Ejemplo n.º 24
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"))
Ejemplo n.º 25
0
 def save_data(self, app_id, data):
     """ Wrapper of the appstorage API. It saves the data in JSON format. """
     app = appstorage.get_app(app_id)
     appstorage.update_app_data(app, data)
Ejemplo n.º 26
0
 def load_data(self, appid):
     """ Wrapper of the appstorage API. It returns a valid JSON file. """
     app = appstorage.get_app(appid)
     return json.loads(app.data)
Ejemplo n.º 27
0
 def get_name(self, appid):
     app = appstorage.get_app(appid)
     return app.name
Ejemplo n.º 28
0
 def get_name(self, appid):
     app = appstorage.get_app(appid)
     return app.name
Ejemplo n.º 29
0
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)
Ejemplo n.º 30
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