Merge branch 'master' into greenkeeper/clean-css-4.0.7

This commit is contained in:
Christopher Grebs 2017-02-20 15:01:38 +01:00 коммит произвёл GitHub
Родитель 55f7b7d312 a7dd334697
Коммит aa334094ec
58 изменённых файлов: 1183 добавлений и 746 удалений

Просмотреть файл

@ -287,6 +287,7 @@ This endpoint allows you to fetch a single version belonging to a specific add-o
:>json int files[].id: The size for a file, in bytes.
:>json int files[].status: The :ref:`status <addon-detail-status>` for a file.
:>json string files[].url: The (absolute) URL to download a file. An optional ``src`` query parameter can be added to indicate the source page (See :ref:`download sources <download-sources>`).
:>json array files[].permissions[]: Array of the webextension permissions for this File, as strings. Empty for non-webextensions.
:>json object license: Object holding information about the license for the version.
:>json string|object|null license.name: The name of the license (See :ref:`translated fields <api-overview-translations>`).
:>json string|object|null license.text: The text of the license (See :ref:`translated fields <api-overview-translations>`).

Просмотреть файл

@ -8,17 +8,21 @@ Reviews
may change without warning. The only authentication method available at
the moment is :ref:`the internal one<api-auth-internal>`.
-------------------
List Add-on reviews
-------------------
------------
List reviews
------------
.. review-list-addon:
.. review-list:
This endpoint allows you to fetch reviews for a given add-on.
This endpoint allows you to fetch reviews for a given add-on or user. Either
``addon`` or ``user`` query parameters are required, and they can be
combined together.
.. http:get:: /api/v3/addons/addon/(int:id|string:slug|string:guid)/reviews/
.. http:get:: /api/v3/reviews/review/
:query string addon: The add-on id to fetch reviews from. When passed, the reviews shown will always be the latest posted by each user on this particular add-on (which means there should only be one review per user in the results).
:query string filter: The :ref:`filter <review-filtering-param>` to apply.
:query string user: The user id to fetch reviews from.
:query int show_grouped_ratings: Whether or not to show ratings aggregates for this add-on in the response.
:>json int count: The number of results for this query.
:>json string next: The URL of the next page of results.
@ -32,23 +36,6 @@ This endpoint allows you to fetch reviews for a given add-on.
can change that with the ``filter=with_deleted`` query parameter, which
requires the Addons:Edit permission.
----------------------
List reviews by a user
----------------------
.. review-list-user:
This endpoint allows you to fetch reviews posted by a specific user.
.. http:get:: /api/v3/accounts/account/(int:id)/reviews/
:query string filter: The :ref:`filter <review-filtering-param>` to apply.
:param int id: The user id.
:>json int count: The number of results for this query.
:>json string next: The URL of the next page of results.
:>json string previous: The URL of the previous page of results.
:>json array results: An array of :ref:`reviews <review-detail-object>`.
------
Detail
------
@ -57,7 +44,7 @@ Detail
This endpoint allows you to fetch a review by its id.
.. http:get:: /api/v3/addons/addon/(int:id|string:slug|string:guid)/reviews/(int:id)/
.. http:get:: /api/v3/reviews/review/(int:id)/
.. _review-detail-object:
@ -89,12 +76,13 @@ If successful a :ref:`review object <review-detail-object>` is returned.
Requires authentication.
.. http:post:: /api/v3/addons/addon/(int:id|string:slug|string:guid)/reviews/
.. http:post:: /api/v3/reviews/review/
:<json string addon: The add-on id the review applies to (required).
:<json string|null body: The text of the review.
:<json string|null title: The title of the review.
:<json int rating: The rating the user wants to give as part of the review (required).
:<json int version: The add-on version id the review applies to.
:<json int version: The add-on version id the review applies to (required).
----
Edit
@ -111,7 +99,7 @@ If successful a :ref:`review object <review-detail-object>` is returned.
Only body, title and rating are allowed for modification.
.. http:patch:: /api/v3/addons/addon/(int:id|string:slug|string:guid)/reviews/(int:id)/
.. http:patch:: /api/v3/reviews/review/(int:id)/
:<json string|null body: The text of the review.
:<json string|null title: The title of the review.
@ -132,7 +120,7 @@ This endpoint allows you to delete an existing review by its id.
not delete a review from somebody else if it was posted on an add-on they
are listed as a developer of.
.. http:delete:: /api/v3/addons/addon/(int:id|string:slug|string:guid)/reviews/(int:id)/
.. http:delete:: /api/v3/reviews/review/(int:id)/
-----
@ -148,7 +136,7 @@ If successful a :ref:`review reply object <review-detail-object>` is returned.
Requires authentication and either Addons:Edit permission or a user account
listed as a developer of the add-on.
.. http:post:: /api/v3/addons/addon/(int:id|string:slug|string:guid)/reviews/(int:id)/reply/
.. http:post:: /api/v3/reviews/review/(int:id)/reply/
:<json string body: The text of the reply (required).
:<json string|null title: The title of the reply.
@ -169,7 +157,7 @@ An empty response will be returned on success.
Requires authentication and a user account different from the one that
posted the review.
.. http:post:: /api/v3/addons/addon/(int:id|string:slug|string:guid)/reviews/(int:id)/flag/
.. http:post:: /api/v3/reviews/review/(int:id)/flag/
:<json string flag: A :ref:`constant<review-flag-constants>` describing the reason behind the flagging.
:<json string|null note: A note to explain further the reason behind the flagging.

Просмотреть файл

@ -1,11 +1,11 @@
#
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT 1.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-02-09 11:33+0000\n"
"PO-Revision-Date: 2017-01-29 11:39+0000\n"
"PO-Revision-Date: 2017-02-16 06:40+0000\n"
"Last-Translator: Bjørn I. <bjorn.svindseth@online.no>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: nn_NO\n"
@ -278,17 +278,13 @@ msgstr "Hjelp til og støtt vidare utvikling av <strong>%(addon_name)s</strong>
#: src/olympia/addons/templates/addons/contributions_lightbox.html
#, python-format
msgid ""
"To show your support for <b>%(addon_name)s</b>, the developer asks that you make a donation to the <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</a>."
msgid "To show your support for <b>%(addon_name)s</b>, the developer asks that you make a donation to the <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</a>."
msgstr "For å visa støtta di til <b>%(addon_name)s</b>, ber utviklaren deg donera til <a href=\"%(charity_url)s\">%(charity_name)s</a> med <a href=\"%(paypal_url)s\">PayPal</a>."
#: src/olympia/addons/templates/addons/contributions_lightbox.html
#, python-format
msgid ""
"To show your support for <b>%(addon_name)s</b>, the developer asks that you make a small contribution to <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</"
"a>."
msgstr ""
"For at visa støtta di til <b>%(addon_name)s</b>, ber udviklaren deg om at du gjev eit mindre bidrag til <a href=\"%(charity_url)s\">%(charity_name)s</a> via <a href=\"%(paypal_url)s\">Paypal</a>."
msgid "To show your support for <b>%(addon_name)s</b>, the developer asks that you make a small contribution to <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</a>."
msgstr "For at visa støtta di til <b>%(addon_name)s</b>, ber udviklaren deg om at du gjev eit mindre bidrag til <a href=\"%(charity_url)s\">%(charity_name)s</a> via <a href=\"%(paypal_url)s\">Paypal</a>."
#: src/olympia/addons/templates/addons/contributions_lightbox.html
msgid "How much would you like to contribute?"
@ -758,8 +754,8 @@ msgstr ""
#: src/olympia/addons/templates/addons/popups.html
msgid ""
"<strong>Caution:</strong> This add-on has not been reviewed by Mozilla and can't be installed on release versions of Firefox 43 and above. Be careful when installing third-party software that "
"might harm your computer."
"<strong>Caution:</strong> This add-on has not been reviewed by Mozilla and can't be installed on release versions of Firefox 43 and above. Be careful when installing third-party software that might"
" harm your computer."
msgstr ""
#: src/olympia/addons/templates/addons/popups.html
@ -968,14 +964,12 @@ msgid "Manage"
msgstr "Handsam"
#: src/olympia/addons/templates/addons/impala/details.html src/olympia/devhub/templates/devhub/includes/addons_edit_nav.html
#, fuzzy
msgid "Add-on Review"
msgstr "Full vurdering av utviding"
msgstr "Vurdering av utviding"
#: src/olympia/addons/templates/addons/impala/details.html src/olympia/devhub/templates/devhub/includes/addons_edit_nav.html
#, fuzzy
msgid "Admin Manage"
msgstr "Handsam"
msgstr "Administrashandsaming"
#. {0} is an add-on name.
#: src/olympia/addons/templates/addons/impala/details.html
@ -1379,9 +1373,8 @@ msgid "Manage API Keys"
msgstr "Handsam API-nøklar"
#: src/olympia/amo/context_processors.py src/olympia/editors/helpers.py src/olympia/editors/templates/editors/base.html
#, fuzzy
msgid "Reviewer Tools"
msgstr "Utviklar"
msgstr "Vurderingsverktøy"
#: src/olympia/amo/context_processors.py
msgid "Admin Tools"
@ -1703,9 +1696,8 @@ msgid "Addon id {0} with GUID {1} has been deleted"
msgstr "Utvidings-ID {0} med GUID {1} er sletta"
#: src/olympia/amo/log.py
#, fuzzy
msgid "{addon} migrated from preliminary."
msgstr "{addon} {version} er førebels vurdert."
msgstr "{addon} migrert frå førebuande."
#: src/olympia/amo/log.py
#, fuzzy
@ -1713,9 +1705,8 @@ msgid "Reply by developer on {addon} {version}."
msgstr "Kommentar på {addon} {version}."
#: src/olympia/amo/log.py
#, fuzzy
msgid "Developer Reply"
msgstr "Utviklar"
msgstr "Utviklarsvar"
#: src/olympia/amo/log.py
#, fuzzy
@ -1766,8 +1757,8 @@ msgstr "Ikkje funne"
#, python-format
msgid ""
"<h1>We're sorry, but we can't find what you're looking for.</h1> <p> The page or file you requested wasn't found on our site. It's possible that you clicked a link that's out of date, or typed in "
"the address incorrectly. </p> <ul> <li>If you typed in the address, please double check the spelling.</li> <li> If you followed a link from somewhere, please <a href=\"https://github.com/mozilla/"
"addons-server/issues/new/\">file an issue</a>. Tell us where you came from and what you were looking for, and we'll do our best to fix it. </li> </ul> <p>Or you can just jump over to some of the "
"the address incorrectly. </p> <ul> <li>If you typed in the address, please double check the spelling.</li> <li> If you followed a link from somewhere, please <a href=\"https://github.com/mozilla"
"/addons-server/issues/new/\">file an issue</a>. Tell us where you came from and what you were looking for, and we'll do our best to fix it. </li> </ul> <p>Or you can just jump over to some of the "
"popular pages on our website.</p> <ul> <li>Are you interested in a <a href=\"%(rec)s\">list of featured add-ons</a>?</li> <li> Do you want to <a href=\"%(search)s\">search for add-ons</a>? You may "
"go to the <a href=\"%(search)s\">search page</a> or just use the search field above. </li> <li> If you prefer to start over, just go to the <a href=\"%(home)s\">add-ons front page</a>. </li> </ul>"
msgstr ""
@ -2275,8 +2266,8 @@ msgstr ""
#: src/olympia/bandwagon/templates/bandwagon/collection_detail.html
msgid ""
"Add-ons that you mark as favorites using the <b>Add to Favorites</b> feature appear below. This collection is currently <b>private</b>, which means only you can see it. If you would like everyone "
"to be able to see your favorites, click the button below to make it public."
"Add-ons that you mark as favorites using the <b>Add to Favorites</b> feature appear below. This collection is currently <b>private</b>, which means only you can see it. If you would like everyone to"
" be able to see your favorites, click the button below to make it public."
msgstr ""
#: src/olympia/bandwagon/templates/bandwagon/collection_detail.html
@ -2935,8 +2926,8 @@ msgstr ""
#: src/olympia/compat/templates/compat/reporter.html src/olympia/compat/templates/compat/reporter_detail.html
#, python-format
msgid ""
"<p> Reports submitted to us through the <a href=\"%(url_)s\">Add-on Compatibility Reporter</a> are collected here for developers to view. These reports help us determine which add-ons will need "
"help supporting an upcoming Firefox version. </p>"
"<p> Reports submitted to us through the <a href=\"%(url_)s\">Add-on Compatibility Reporter</a> are collected here for developers to view. These reports help us determine which add-ons will need help"
" supporting an upcoming Firefox version. </p>"
msgstr ""
#: src/olympia/compat/templates/compat/reporter.html
@ -2993,9 +2984,8 @@ msgid "e10s Enabled"
msgstr "e10s slått på"
#: src/olympia/compat/templates/compat/reporter_detail.html
#, fuzzy
msgid "e10s Compatible"
msgstr "Kompatibel"
msgstr "e10s kompatibel"
#: src/olympia/compat/templates/compat/reporter_detail.html src/olympia/editors/templates/editors/themes/themes.html
msgid "Submitted"
@ -3207,9 +3197,8 @@ msgid "Updated Dictionary Review"
msgstr ""
#: src/olympia/constants/base.py
#, fuzzy
msgid "New Language Pack Review"
msgstr "Språkpakke"
msgstr "Ny språkpakke-vurdering"
#: src/olympia/constants/base.py
msgid "Preliminary Language Pack Review"
@ -3334,9 +3323,8 @@ msgid "Games & Entertainment"
msgstr "Spel og underhaldning"
#: src/olympia/constants/categories.py
#, fuzzy
msgid "Language Support"
msgstr "Språk"
msgstr "Språkstøtte"
#: src/olympia/constants/categories.py
msgid "Photos, Music & Videos"
@ -3359,9 +3347,8 @@ msgid "Tabs"
msgstr "Faner"
#: src/olympia/constants/categories.py
#, fuzzy
msgid "Web Development"
msgstr "Vis utviklingsversjon"
msgstr "Nettutvikling"
#: src/olympia/constants/categories.py
msgid "Animals"
@ -3384,9 +3371,8 @@ msgid "Modern"
msgstr "Moderne"
#: src/olympia/constants/categories.py
#, fuzzy
msgid "Nature"
msgstr "Framheva"
msgstr "Natur"
#: src/olympia/constants/categories.py
msgid "OS Integration"
@ -3545,9 +3531,8 @@ msgid "Message Composition"
msgstr "Gje bidrag"
#: src/olympia/constants/categories.py
#, fuzzy
msgid "Contacts"
msgstr "Innhald"
msgstr "Kontaktar"
#: src/olympia/constants/categories.py
msgid "Folders and Filters"
@ -3559,7 +3544,7 @@ msgstr "Import/eksport"
#: src/olympia/constants/categories.py
msgid "Message and News Reading"
msgstr ""
msgstr "Meldings- og nyhendelesing"
#: src/olympia/constants/categories.py
msgid "Privacy and Security"
@ -3914,8 +3899,7 @@ msgid ""
msgstr ""
#: src/olympia/devhub/forms.py
msgid ""
"On your own. <span class=\"helptext\">Your submission will be immediately signed for self-distribution. Updates should be handled by you via an updateURL or external application updates.</span>"
msgid "On your own. <span class=\"helptext\">Your submission will be immediately signed for self-distribution. Updates should be handled by you via an updateURL or external application updates.</span>"
msgstr ""
#: src/olympia/devhub/helpers.py
@ -4193,9 +4177,9 @@ msgstr "Lær alt om utvidingar"
#: src/olympia/devhub/templates/devhub/index-legacy.html
msgid ""
"Add-ons let millions of Firefox users enhance and customize their browsing experience. If you're a Web developer and know <a href=\"https://developer.mozilla.org/docs/Web/HTML\">HTML</a>, <a href="
"\"https://developer.mozilla.org/docs/Web/JavaScript\">JavaScript</a>, and <a href=\"https://developer.mozilla.org/docs/Web/CSS\">CSS</a>, you already have all the necessary skills to make a great "
"add-on."
"Add-ons let millions of Firefox users enhance and customize their browsing experience. If you're a Web developer and know <a href=\"https://developer.mozilla.org/docs/Web/HTML\">HTML</a>, <a "
"href=\"https://developer.mozilla.org/docs/Web/JavaScript\">JavaScript</a>, and <a href=\"https://developer.mozilla.org/docs/Web/CSS\">CSS</a>, you already have all the necessary skills to make a "
"great add-on."
msgstr ""
#: src/olympia/devhub/templates/devhub/index-legacy.html
@ -4375,9 +4359,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/ajax_compat_error.html
#, python-format
msgid ""
"<a href=\"#\" class=\"button compat-update\" data-updateurl=\"%(update_url)s\">Update Compatibility</a> <a href=\"%(version_url)s\" class=\"button\">Upload New Version</a> or <a href=\"#\" class="
"\"close\">Ignore</a>"
msgid "<a href=\"#\" class=\"button compat-update\" data-updateurl=\"%(update_url)s\">Update Compatibility</a> <a href=\"%(version_url)s\" class=\"button\">Upload New Version</a> or <a href=\"#\" class=\"close\">Ignore</a>"
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/ajax_compat_status.html
@ -4430,9 +4412,8 @@ msgstr[0] ""
msgstr[1] ""
#: src/olympia/devhub/templates/devhub/addons/edit.html src/olympia/devhub/templates/devhub/addons/listing/item_actions.html src/olympia/devhub/templates/devhub/includes/addons_edit_nav.html
#, fuzzy
msgid "Edit Information"
msgstr "Versjonsinformasjon"
msgstr "Rediger informasjon"
#: src/olympia/devhub/templates/devhub/addons/edit.html src/olympia/devhub/templates/devhub/includes/macros.html src/olympia/translations/templates/translations/all-locales.html
msgid "None"
@ -4515,9 +4496,8 @@ msgid ""
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/basic.html
#, fuzzy
msgid "Experimental?"
msgstr "Eksperimentell"
msgstr "Eksperimentell?"
#: src/olympia/devhub/templates/devhub/addons/edit/basic.html
msgid "If your add-on on is experimental or otherwise not ready for general use. The add-on will be listed but will have reduced visibility. "
@ -4537,8 +4517,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/basic.html
#, python-format
msgid ""
"Categories cannot be changed while your add-on is featured for this application. Please email <a href=\"mailto:%(email)s\">%(email)s</a> if there is a reason you need to modify your categories."
msgid "Categories cannot be changed while your add-on is featured for this application. Please email <a href=\"mailto:%(email)s\">%(email)s</a> if there is a reason you need to modify your categories."
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/basic.html
@ -4606,8 +4585,8 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/support.html
msgid ""
"If you wish to display an e-mail address for support inquiries, enter it here. If you have different addresses for each language multiple translations of this field can be added. It is only "
"relevant for listed add-ons."
"If you wish to display an e-mail address for support inquiries, enter it here. If you have different addresses for each language multiple translations of this field can be added. It is only relevant"
" for listed add-ons."
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/support.html
@ -4964,24 +4943,20 @@ msgstr[0] ""
msgstr[1] ""
#: src/olympia/devhub/templates/devhub/addons/submit/base.html
#, fuzzy
msgid "Submit a New File"
msgstr "Send inn eit nytt tema"
msgstr "Send inn ei ny fil"
#: src/olympia/devhub/templates/devhub/addons/submit/base.html
#, fuzzy
msgid "Submit a New Version"
msgstr "Send inn ei ny utviding"
msgstr "Send inn ein ny versjon"
#: src/olympia/devhub/templates/devhub/addons/submit/describe.html src/olympia/devhub/templates/devhub/addons/submit/describe_minimal.html
#, fuzzy
msgid "Describe Add-on"
msgstr "Fleire utvidingar"
msgstr "Beskriv utvidinga"
#: src/olympia/devhub/templates/devhub/addons/submit/describe.html
#, fuzzy
msgid "Add-on URL:"
msgstr "Utviding"
msgstr "URL for utviding:"
#: src/olympia/devhub/templates/devhub/addons/submit/describe.html src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "Please use only letters, numbers, underscores, and dashes in your URL."
@ -5184,9 +5159,8 @@ msgid "Administrative overrides"
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/submit/upload.html
#, fuzzy
msgid "Submit File"
msgstr "Send inn eit nytt tema"
msgstr "Send inn fil"
#: src/olympia/devhub/templates/devhub/addons/submit/upload.html
#, fuzzy
@ -5221,8 +5195,8 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/api/key.html
#, python-format
msgid ""
"To make API requests, send a <a href=\"%(jwt_url)s\">JSON Web Token (JWT)</a> as the authorization header. You'll need to generate a JWT for every request as explained in the <a href=\"%(docs_url)s"
"\">API documentation</a>."
"To make API requests, send a <a href=\"%(jwt_url)s\">JSON Web Token (JWT)</a> as the authorization header. You'll need to generate a JWT for every request as explained in the <a "
"href=\"%(docs_url)s\">API documentation</a>."
msgstr ""
#: src/olympia/devhub/templates/devhub/api/key.html
@ -5442,28 +5416,24 @@ msgid "{0} Add-ons"
msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/blog_posts.html
#, fuzzy
msgid "Latest News"
msgstr "Siste versjon:"
msgstr "Siste nytt"
#: src/olympia/devhub/templates/devhub/new-landing/components/blog_posts.html
msgid "Read more in our Blog"
msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
msgid "Resources"
msgstr "Fleire resursar"
msgstr "Resursar"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
msgid "WebExtensions"
msgstr "Utvidingar"
msgstr "WebUtvidingar"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
msgid "Community"
msgstr "Kommentar"
msgstr "Fellesskap"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html src/olympia/templates/footer.html
msgid "About"
@ -5478,9 +5448,8 @@ msgid "Blog"
msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
msgid "Contact Us"
msgstr "Innhald"
msgstr "Kontakt oss"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
@ -5492,9 +5461,8 @@ msgid "FAQ"
msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
msgid "Report Bug"
msgstr "Rapporter misbruk"
msgstr "Rapporter feil"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html src/olympia/templates/copyright.html
msgid "Site Status"
@ -5536,9 +5504,8 @@ msgid "Legal Notices"
msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
msgid "Review Add-ons"
msgstr "Fleire utvidingar"
msgstr "Vurder utvidingar"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
msgid "Volunteer reviewers help keep add-ons safe and reliable to use. They enjoy great perks too!"
@ -5592,14 +5559,12 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/manage_your_addon.html src/olympia/devhub/templates/devhub/new-landing/components/my-addons.html
#: src/olympia/devhub/templates/devhub/new-landing/components/port_chrome_ext.html
#, fuzzy
msgid "Learn How"
msgstr "Les meir"
msgstr "Lær deg korleis"
#: src/olympia/devhub/templates/devhub/new-landing/components/my-addons.html
#, fuzzy
msgid "What's New"
msgstr "Siste versjon:"
msgstr "Kva er nytt"
#: src/olympia/devhub/templates/devhub/new-landing/components/my-addons.html
msgid "Happy New Year! See what's in store for add-ons in 2017."
@ -5659,8 +5624,8 @@ msgstr "Tilpass Firefox"
#: src/olympia/devhub/templates/devhub/new-landing/components/overview.html
#, python-format
msgid ""
"Add-ons let millions of Firefox users enhance their browsing experience. If you know <a href=\"%(html_link)s\">HTML</a>, <a href=\"%(js_link)s\">JavaScript</a>, and <a href=\"%(css_link)s\">CSS</"
"a>, you already have all the necessary skills to make a great add-on."
"Add-ons let millions of Firefox users enhance their browsing experience. If you know <a href=\"%(html_link)s\">HTML</a>, <a href=\"%(js_link)s\">JavaScript</a>, and <a href=\"%(css_link)s\">CSS</a>,"
" you already have all the necessary skills to make a great add-on."
msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/overview.html
@ -5693,9 +5658,8 @@ msgid "Sign In"
msgstr "Utviding"
#: src/olympia/devhub/templates/devhub/new-landing/components/tools.html
#, fuzzy
msgid "Validator"
msgstr "Valider no."
msgstr "Validator"
#: src/olympia/devhub/templates/devhub/new-landing/components/tools.html
msgid "Automated code tests for your add-on."
@ -6463,11 +6427,10 @@ msgstr[0] "Utvidingar for {0}"
msgstr[1] "Utvidingar for {0}"
#: src/olympia/editors/helpers.py
#, fuzzy
msgid "Update ({0})"
msgid_plural "Updates ({0})"
msgstr[0] "Oppdatert {0}"
msgstr[1] "Oppdatert {0}"
msgstr[0] "Oppdatering ({0})"
msgstr[1] "Oppdateringar ({0})"
#: src/olympia/editors/helpers.py
msgid "Moderated Review ({0})"
@ -6704,9 +6667,8 @@ msgid "Queues"
msgstr ""
#: src/olympia/editors/templates/editors/base.html
#, fuzzy
msgid "New Add-ons"
msgstr "Fleire utvidingar"
msgstr "Nye utvidingar"
#: src/olympia/editors/templates/editors/base.html
msgid "Moderated Reviews"
@ -6819,9 +6781,8 @@ msgid "Reviews This Month"
msgstr ""
#: src/olympia/editors/templates/editors/home.html
#, fuzzy
msgid "New Reviewers"
msgstr "AMO-vurderarar"
msgstr "Nye vurderarar"
#: src/olympia/editors/templates/editors/home.html
msgid "You're #{0} with {1} reviews"
@ -8437,8 +8398,7 @@ msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
#, python-format
msgid ""
"Help improve this website. It's open source, and you can file bugs and submit patches. <a href=\"%(url)s\"> GitHub</a> contains all of our current bugs, legacy bugs can still be found in Bugzilla."
msgid "Help improve this website. It's open source, and you can file bugs and submit patches. <a href=\"%(url)s\"> GitHub</a> contains all of our current bugs, legacy bugs can still be found in Bugzilla."
msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
@ -8464,9 +8424,7 @@ msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
#, python-format
msgid ""
"A good place to start is our <a href=\"%(faq_url)s\"><abbr title=\"Frequently Asked Questions\">FAQ</abbr></a>. If you don't find an answer there, you can <a href=\"%(forum_url)s\"> ask on our "
"forums</a>."
msgid "A good place to start is our <a href=\"%(faq_url)s\"><abbr title=\"Frequently Asked Questions\">FAQ</abbr></a>. If you don't find an answer there, you can <a href=\"%(forum_url)s\"> ask on our forums</a>."
msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
@ -8481,8 +8439,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
#, python-format
msgid ""
"Over the years, many people have contributed to this website, including both volunteers from the community and a dedicated AMO team. A list of significant contributors can be found on our <a href="
"\"%(url)s\"> Site Credits</a> page."
"Over the years, many people have contributed to this website, including both volunteers from the community and a dedicated AMO team. A list of significant contributors can be found on our <a "
"href=\"%(url)s\"> Site Credits</a> page."
msgstr ""
#: src/olympia/pages/templates/pages/acr_firstrun.html
@ -8696,8 +8654,8 @@ msgstr ""
#, python-format
msgid ""
"This file, called an <a href=\"%(url)s\">Install Manifest</a>, is used by Add-on Manager-enabled XUL applications to determine information about an add-on as it is being installed. It contains "
"metadata identifying the add-on, providing information about who created it, where more information can be found about it, which versions of what applications it is compatible with, how it should "
"be updated, and so on. The format of the Install Manifest is RDF/XML."
"metadata identifying the add-on, providing information about who created it, where more information can be found about it, which versions of what applications it is compatible with, how it should be"
" updated, and so on. The format of the Install Manifest is RDF/XML."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -8744,9 +8702,9 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
msgid ""
"To ensure compatibility with the latest Mozilla software, it's important to download updates as they become available and test your add-on to ensure that it is still functioning as expected. In "
"many cases, the latest version of Mozilla software may be a beta release. Since these releases at times introduce architectural changes that may impact the functionality of your add-on, it's "
"important to be actively involved in the beta process to ensure that your add-on users are not negatively impacted upon final release of Mozilla software."
"To ensure compatibility with the latest Mozilla software, it's important to download updates as they become available and test your add-on to ensure that it is still functioning as expected. In many"
" cases, the latest version of Mozilla software may be a beta release. Since these releases at times introduce architectural changes that may impact the functionality of your add-on, it's important "
"to be actively involved in the beta process to ensure that your add-on users are not negatively impacted upon final release of Mozilla software."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -8756,8 +8714,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"Poorly written extensions can have a severe impact on the browsing experience, including on the overall performance of Firefox itself. The following page contains many good <a href=\"%(url)s"
"\">guides</a> that help you improve performance, whether you're developing core Mozilla code or an add-on."
"Poorly written extensions can have a severe impact on the browsing experience, including on the overall performance of Firefox itself. The following page contains many good <a "
"href=\"%(url)s\">guides</a> that help you improve performance, whether you're developing core Mozilla code or an add-on."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -8828,8 +8786,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"Yes. You may find 3rd party developers via the <a href=\"%(forum_url)s\">Add-ons forum</a>, <a href=\"%(list_url)s\">mozilla.jobs list</a>, <a href=\"%(mz_url)s\">mozillaZine forums</a> or <a href="
"\"%(wiki_url)s\">the Mozilla Wiki</a>. Please note that Mozilla does not offer developer recommendations."
"Yes. You may find 3rd party developers via the <a href=\"%(forum_url)s\">Add-ons forum</a>, <a href=\"%(list_url)s\">mozilla.jobs list</a>, <a href=\"%(mz_url)s\">mozillaZine forums</a> or <a "
"href=\"%(wiki_url)s\">the Mozilla Wiki</a>. Please note that Mozilla does not offer developer recommendations."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -8918,8 +8876,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
msgid ""
"The choice of category is dependent on what type of audience you are targeting and the functionality of your add-on. If you're unsure of which category your add-on falls into, please choose \"Other"
"\". The AMO team may re-categorize your add-on if it's determined that it's better suited in a different category."
"The choice of category is dependent on what type of audience you are targeting and the functionality of your add-on. If you're unsure of which category your add-on falls into, please choose "
"\"Other\". The AMO team may re-categorize your add-on if it's determined that it's better suited in a different category."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -8956,9 +8914,9 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"Add-ons are reviewed by the Add-on Reviewers, a group of talented developers that volunteer to help the Mozilla project by reviewing add-ons to ensure a stable and safe experience for Mozilla "
"users. When communicating with reviewer, please be courteous, patient and respectful as they are working hard to ensure that your add-on is set up correctly and follows the guidelines outlined in "
"the <a href=\"%(url)s\">Add-on Review Guide</a>."
"Add-ons are reviewed by the Add-on Reviewers, a group of talented developers that volunteer to help the Mozilla project by reviewing add-ons to ensure a stable and safe experience for Mozilla users."
" When communicating with reviewer, please be courteous, patient and respectful as they are working hard to ensure that your add-on is set up correctly and follows the guidelines outlined in the <a "
"href=\"%(url)s\">Add-on Review Guide</a>."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -8998,8 +8956,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"This is why it's very important to read the <a href=\"%(g_url)s\">Add-on Review Guide</a> to ensure that your add-on is setup as expected. It's also a good idea to read the blog post, <a href="
"\"%(blog_url)s\">Successfully Getting your Add-on Reviewed</a> which provides excellent insight into ensuring a smooth review of your add-on."
"This is why it's very important to read the <a href=\"%(g_url)s\">Add-on Review Guide</a> to ensure that your add-on is setup as expected. It's also a good idea to read the blog post, <a "
"href=\"%(blog_url)s\">Successfully Getting your Add-on Reviewed</a> which provides excellent insight into ensuring a smooth review of your add-on."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9087,10 +9045,10 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
msgid ""
"Do you need more information about the various open source licenses? Are you confused as to which license you should select? What rights does a specific license grant? While nothing replaces "
"reading the full terms of a license, below are some sites that contain information about some of the key open source licenses that may help you sort out the differences between them. These sites "
"are being provided solely for your convenience and as a reference for your personal use. These resources do not constitute legal advice nor should they be used in lieu of such advice. Mozilla "
"neither guarantees nor is responsible for the content of these sites or your reliance on such content."
"Do you need more information about the various open source licenses? Are you confused as to which license you should select? What rights does a specific license grant? While nothing replaces reading"
" the full terms of a license, below are some sites that contain information about some of the key open source licenses that may help you sort out the differences between them. These sites are being "
"provided solely for your convenience and as a reference for your personal use. These resources do not constitute legal advice nor should they be used in lieu of such advice. Mozilla neither "
"guarantees nor is responsible for the content of these sites or your reliance on such content."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9148,9 +9106,10 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"Add-ons listed in this gallery only work with Mozilla-based applications, such as <a href=\"%(getfirefox_url)s\">Firefox</a>, <a href=\"%(getmobile_url)s\">Firefox Mobile</a>, <a href="
"\"%(getseamonkey_url)s\">SeaMonkey</a>, and <a href=\"%(getthunderbird_url)s\">Thunderbird</a>. However, not all add-ons work with each of those applications or every version of those applications. "
"Each add-on specifies which applications and versions it works with, such as Firefox 2.0 - 3.6.*. For Firefox add-ons, the install buttons will indicate whether the add-on is compatible or not."
"Add-ons listed in this gallery only work with Mozilla-based applications, such as <a href=\"%(getfirefox_url)s\">Firefox</a>, <a href=\"%(getmobile_url)s\">Firefox Mobile</a>, <a "
"href=\"%(getseamonkey_url)s\">SeaMonkey</a>, and <a href=\"%(getthunderbird_url)s\">Thunderbird</a>. However, not all add-ons work with each of those applications or every version of those "
"applications. Each add-on specifies which applications and versions it works with, such as Firefox 2.0 - 3.6.*. For Firefox add-ons, the install buttons will indicate whether the add-on is "
"compatible or not."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9213,8 +9172,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"In Firefox, add-ons marked with \"No restart required\" can be installed without restarting. These add-ons have been created using the <a href=\"%(sdk_url)s\">Add-on SDK</a> or <a href="
"\"%(bootstrap_url)s\">bootstrapping</a>. Other add-ons will still require a restart before you can use them."
"In Firefox, add-ons marked with \"No restart required\" can be installed without restarting. These add-ons have been created using the <a href=\"%(sdk_url)s\">Add-on SDK</a> or <a "
"href=\"%(bootstrap_url)s\">bootstrapping</a>. Other add-ons will still require a restart before you can use them."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9225,8 +9184,8 @@ msgstr ""
#, python-format
msgid ""
"Add-ons, unlike plugins, are automatically checked for updates once every day. In Firefox, updates are automatically installed by default. Versions of Firefox prior to 4 (and other applications) "
"will alert you that updates to your add-ons are available. <a href=\"%(plugin_url)s\">Plugins</a> are not currently automatically checked for updates, so be sure to regularly visit the <a href="
"\"%(plugincheck_url)s\">Plugin Check</a> page to stay up-to-date."
"will alert you that updates to your add-ons are available. <a href=\"%(plugin_url)s\">Plugins</a> are not currently automatically checked for updates, so be sure to regularly visit the <a "
"href=\"%(plugincheck_url)s\">Plugin Check</a> page to stay up-to-date."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9237,8 +9196,8 @@ msgstr ""
#, python-format
msgid ""
"Unless clearly marked otherwise, add-ons available from this gallery have been checked and approved by Mozilla's team of editors and are safe to install. We recommend that you only install approved "
"add-ons. If you wish to install unapproved add-ons or add-ons from third-party websites, use caution as these add-ons may harm your computer or violate your privacy. <a href=\"%(learnmore_url)s"
"\">Learn more about our approval process</a>"
"add-ons. If you wish to install unapproved add-ons or add-ons from third-party websites, use caution as these add-ons may harm your computer or violate your privacy. <a "
"href=\"%(learnmore_url)s\">Learn more about our approval process</a>"
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9322,8 +9281,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"While all add-ons publicly available in our gallery are reviewed by an editor, you may receive a direct link to an add-on that hasn't yet been reviewed. Use caution when installing these add-ons, "
"as they could harm your computer or violate your privacy. We recommend that you only install reviewed add-ons. <a href=\"%(url)s\">Learn more about our review process</a></dd>"
"While all add-ons publicly available in our gallery are reviewed by an editor, you may receive a direct link to an add-on that hasn't yet been reviewed. Use caution when installing these add-ons, as"
" they could harm your computer or violate your privacy. We recommend that you only install reviewed add-ons. <a href=\"%(url)s\">Learn more about our review process</a></dd>"
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9350,8 +9309,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
msgid ""
"Beta add-ons are unreviewed versions which represent the latest work of an add-on author. While different authors have different standards for beta code quality, you should assume that these add-"
"ons are less stable than the general add-on releases."
"Beta add-ons are unreviewed versions which represent the latest work of an add-on author. While different authors have different standards for beta code quality, you should assume that these add-ons"
" are less stable than the general add-on releases."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9394,9 +9353,9 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"The source code used to create an add-on is an exclusive copyright of the add-on author, unless otherwise declared in a source code license. Many add-ons on this site have <a href=\"%(url)s\" lang="
"\"en\">open source licenses</a> that make the source code publicly available for copy and reuse under conditions determined by the author. Most authors choose widely known open source licenses like "
"the GPL or BSD licenses instead of making up their own."
"The source code used to create an add-on is an exclusive copyright of the add-on author, unless otherwise declared in a source code license. Many add-ons on this site have <a href=\"%(url)s\" "
"lang=\"en\">open source licenses</a> that make the source code publicly available for copy and reuse under conditions determined by the author. Most authors choose widely known open source licenses "
"like the GPL or BSD licenses instead of making up their own."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9435,8 +9394,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"Mobile add-ons work with <a href=\"%(mobile_url)s\">Firefox for Mobile</a> and add or modify functionality just like desktop add-ons. You can find add-ons that work with Firefox for Mobile in our "
"<a href=\"%(gallery_url)s\">gallery</a>."
"Mobile add-ons work with <a href=\"%(mobile_url)s\">Firefox for Mobile</a> and add or modify functionality just like desktop add-ons. You can find add-ons that work with Firefox for Mobile in our <a"
" href=\"%(gallery_url)s\">gallery</a>."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9467,8 +9426,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/review_guide.html
msgid ""
"Add-on reviews are a way for you to share your opinions about the add-ons youve installed and used. Our review moderation team reserves the right to refuse or remove any review that does not "
"comply with these guidelines."
"Add-on reviews are a way for you to share your opinions about the add-ons youve installed and used. Our review moderation team reserves the right to refuse or remove any review that does not comply"
" with these guidelines."
msgstr ""
#: src/olympia/pages/templates/pages/review_guide.html
@ -9673,10 +9632,10 @@ msgstr ""
msgid ""
"<h2>Keep these tips in mind:</h2> <ul> <li> Write like you're telling a friend about your experience with the add-on. Give specifics and helpful details, such as what features you liked and/or "
"disliked, how easy to use it is, and any disadvantages it has. Avoid generic language such as calling it \"Great\" or \"Bad\" unless you can give reasons why you believe this is so. </li> <li> "
"Please do not post bug reports here. We do not make your email address available to add-on developers, so they can't contact you to resolve your issue. See this add-on's <a href=\"%(support)s"
"\">support section</a> to find out if assistance is available. You can also try asking the <a href=\"https://discourse.mozilla-community.org/c/add-ons/add-on-support\">add-on community</a> for "
"help. </li> <li>Please keep reviews clean, avoid the use of improper language and do not post any personal information. </li> </ul> <p>Please read the <a href=\"%(guide)s\">Review Guidelines</a> "
"for more detail about user add-on reviews.</p>"
"Please do not post bug reports here. We do not make your email address available to add-on developers, so they can't contact you to resolve your issue. See this add-on's <a "
"href=\"%(support)s\">support section</a> to find out if assistance is available. You can also try asking the <a href=\"https://discourse.mozilla-community.org/c/add-ons/add-on-support\">add-on "
"community</a> for help. </li> <li>Please keep reviews clean, avoid the use of improper language and do not post any personal information. </li> </ul> <p>Please read the <a href=\"%(guide)s\">Review "
"Guidelines</a> for more detail about user add-on reviews.</p>"
msgstr ""
#: src/olympia/reviews/templates/reviews/reply.html
@ -10307,11 +10266,11 @@ msgstr ""
#: src/olympia/stats/templates/stats/reports/sources.html
#, python-format
msgid ""
"<h2>Tracking external sources</h2> <p> If you link to your add-on's details page or directly to its file from an external site, such as your blog or website, you can append a parameter to be "
"tracked as an additional download source on this page. For example, the following links would appear as sourced by your blog: <dl> <dt>Add-on Details Page</dt> <dd>https://addons.mozilla.org/addon/"
"%(slug)s?src=<b>external-blog</b></dd> <dt>Direct File Link</dt> <dd>https://addons.mozilla.org/downloads/latest/%(id)s/addon-%(id)s-latest.xpi?src=<b>external-blog</b></dd> </dl> <p> Only src "
"parameters that begin with \"external-\" will be tracked, up to 61 additional characters. Any text after \"external-\" can be used to describe the source, such as \"external-blog\", \"external-"
"sidebar\", \"external-campaign225\", etc. The following URL-safe characters are allowed: <code>a-z A-Z - . _ ~ %% +</code>"
"<h2>Tracking external sources</h2> <p> If you link to your add-on's details page or directly to its file from an external site, such as your blog or website, you can append a parameter to be tracked"
" as an additional download source on this page. For example, the following links would appear as sourced by your blog: <dl> <dt>Add-on Details Page</dt> "
"<dd>https://addons.mozilla.org/addon/%(slug)s?src=<b>external-blog</b></dd> <dt>Direct File Link</dt> <dd>https://addons.mozilla.org/downloads/latest/%(id)s/addon-%(id)s-latest.xpi?src=<b>external-"
"blog</b></dd> </dl> <p> Only src parameters that begin with \"external-\" will be tracked, up to 61 additional characters. Any text after \"external-\" can be used to describe the source, such as "
"\"external-blog\", \"external-sidebar\", \"external-campaign225\", etc. The following URL-safe characters are allowed: <code>a-z A-Z - . _ ~ %% +</code>"
msgstr ""
#: src/olympia/stats/templates/stats/reports/statuses.html

Просмотреть файл

@ -1,11 +1,11 @@
#
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT 1.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-02-09 11:33+0000\n"
"PO-Revision-Date: 2016-11-07 14:54+0000\n"
"PO-Revision-Date: 2017-02-16 01:59+0000\n"
"Last-Translator: Bjørn I. <bjorn.svindseth@online.no>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: nn_NO\n"
@ -144,17 +144,19 @@ msgstr "Sjekkliste for å senda inn utvidingar"
#: static/js/common/upload-addon.js
msgid "Please verify the following points before finalizing your submission. This will minimize delays or misunderstanding during the review process:"
msgstr ""
msgstr "Gjer vel og kontroller følgjande punkt før du fullfører bidraget ditt. Dette vil minimera forseinkingar eller misforståingar under vurderingsprosessen:"
#: static/js/common/upload-addon.js
msgid ""
"Compiled binaries, as well as minified or obfuscated scripts (excluding known libraries) need to have their sources submitted separately for review. Make sure that you use the source code upload "
"field to avoid having your submission rejected."
msgstr ""
"Kompilerte binærfiler, så vel som forminska eller forvrengde skript (unnateke kjende bibliotek) må senda inn kjeldene sine separat for vurdering. Sjå til at du brukar opplastingsfeltet for "
"kjeldekoden slik at du unngår at bidraget ditt vert avvist."
#: static/js/common/upload-addon.js
msgid "The validation process found these issues that can lead to rejections:"
msgstr ""
msgstr "Valideringsprosessen fann desse problema som kan føra til avslag:"
#: static/js/common/upload-addon.js
msgid "Sorry, no supported platform has been found."
@ -815,7 +817,7 @@ msgstr "Vel ein applikasjon først"
msgid "Set {0} add-on to a max version of {1} and email the author."
msgid_plural "Set {0} add-ons to a max version of {1} and email the authors."
msgstr[0] "Set utvidinga {0} til maksversjon på {1} og send e-post til utviklaren."
msgstr[1] ""
msgstr[1] "Set utvidingane {0} til maksversjon på {1} og send e-post til utviklaren."
#: static/js/zamboni/admin_validation.js
msgid "Email author of {2} add-on which failed validation."

Просмотреть файл

@ -4,8 +4,8 @@ msgstr ""
"Project-Id-Version: AMO (olympia)\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-02-09 11:33+0000\n"
"PO-Revision-Date: 2017-02-13 15:30+0000\n"
"Last-Translator: Alberto Castro <albertdecastro@tutanota.com>\n"
"PO-Revision-Date: 2017-02-15 15:03+0000\n"
"Last-Translator: Rodrigo <rodrigo.mcunha@hotmail.com>\n"
"Language-Team: pt-PT <portuguese european>\n"
"Language: pt_PT\n"
"MIME-Version: 1.0\n"
@ -5400,7 +5400,7 @@ msgstr "Ver todas"
#: src/olympia/devhub/templates/devhub/includes/addons_edit_nav.html
msgid "View Statistics Dashboard"
msgstr "Ver o quadro de estatísticas"
msgstr "Ver o painel de estatísticas"
# 75%
#: src/olympia/devhub/templates/devhub/includes/addons_edit_nav.html
@ -10337,7 +10337,7 @@ msgstr "Exportar"
#. {0} is an add-on name, {1} is an app name (like Firefox)
#: src/olympia/stats/templates/stats/stats.html
msgid "{0} :: Statistics Dashboard :: Add-ons for {1}"
msgstr "{0} ::Quadro de estatísticas :: Extras para o {1}"
msgstr "{0} :: Painel de estatísticas :: Extras para {1}"
# 77%
#: src/olympia/stats/templates/stats/stats.html
@ -10421,7 +10421,7 @@ msgstr "Sem dados disponíveis."
#: src/olympia/stats/templates/stats/stats.html
msgid "This dashboard is currently <b>public</b>."
msgstr "Este quadro de momento é <b>público</b>."
msgstr "Este painel é atualmente <b>público</b>."
#: src/olympia/stats/templates/stats/stats.html
msgid "Contribution stats are currently <b>private</b>."
@ -10497,7 +10497,7 @@ msgstr "A carregar..."
#: src/olympia/stats/templates/stats/reports/overview.html
msgid "<b>{0}</b> Average Daily Users"
msgstr "<b>{0}</b> Utilizadores médios diários"
msgstr "<b>{0}</b> utilizadores médios diários"
#: src/olympia/stats/templates/stats/reports/overview.html
msgid "Top Applications"
@ -10927,7 +10927,7 @@ msgstr "o meu extra baseado no sdk não pode ser atualizado"
#: src/olympia/users/notifications.py
msgid "my add-on is reviewed by a reviewer"
msgstr "o meu extra é revisto por um editor"
msgstr "o meu extra é revisto por um revisor"
#: src/olympia/users/notifications.py
msgid "Mozilla needs to contact me about my individual add-on"

Просмотреть файл

@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: addons\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-02-09 11:33+0000\n"
"PO-Revision-Date: 2017-02-11 18:39+0000\n"
"PO-Revision-Date: 2017-02-14 19:24+0000\n"
"Last-Translator: Top <teerapatxtop@yahoo.com>\n"
"Language-Team: Thai\n"
"Language: th\n"
@ -1891,7 +1891,7 @@ msgstr "ก่อนหน้า"
#: src/olympia/amo/templates/amo/impala/paginator.html
msgid "Jump to last page"
msgstr ""
msgstr "กระโดดไปยังหน้าสุดท้าย"
#. First and second arguments are the result range (e.g., 1-20);
#. third argument is the number of total results (e.g., 1,000).
@ -2294,7 +2294,7 @@ msgstr[0] "<span>%(addons)s</span> ส่วนเสริม"
#, python-format
msgid "<span>%(followers)s</span> follower"
msgid_plural "<span>%(followers)s</span> followers"
msgstr[0] ""
msgstr[0] "<span>%(followers)s</span> ผู้ติดตาม"
#. People "follow" collections to get notified of updates. Like
#. Twitter followers.
@ -2352,11 +2352,11 @@ msgstr "คุณแน่ใจหรือไม่?"
#: src/olympia/bandwagon/templates/bandwagon/delete.html src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "Yes"
msgstr ""
msgstr "ใช่"
#: src/olympia/bandwagon/templates/bandwagon/delete.html src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "No"
msgstr ""
msgstr "ไม่"
#. {0} is the name of the collection.
#: src/olympia/bandwagon/templates/bandwagon/edit.html
@ -2749,7 +2749,7 @@ msgstr "ค้นหาผู้ให้บริการใหม่สำห
#: src/olympia/browse/templates/browse/search_tools.html
msgid "Additional Resources"
msgstr ""
msgstr "ทรัพยากรเพิ่มเติม"
#. {0} is an app name, like Firefox.
#: src/olympia/browse/templates/browse/search_tools.html
@ -3023,7 +3023,7 @@ msgstr "Sunbird"
#: src/olympia/constants/applications.py
msgid "Mobile"
msgstr ""
msgstr "มือถือ"
#: src/olympia/constants/applications.py
msgid "Firefox for Android"
@ -3092,7 +3092,7 @@ msgstr "ชุดตกแต่งแบบสมบูรณ์"
#: src/olympia/constants/base.py
msgid "Search Engine"
msgstr ""
msgstr "เครื่องมือค้นหา"
#: src/olympia/constants/base.py
msgid "Language Pack (Application)"
@ -3100,7 +3100,7 @@ msgstr "ชุดภาษา (แอพพลิเคชัน)"
#: src/olympia/constants/base.py
msgid "Language Pack (Add-on)"
msgstr ""
msgstr "ชุดภาษา (ส่วนเสริม)"
#: src/olympia/constants/base.py
msgid "Plugin"
@ -3125,7 +3125,7 @@ msgstr "ชุดภาษา (แอพพลิเคชัน)"
#: src/olympia/constants/base.py
msgid "Language Packs (Add-on)"
msgstr ""
msgstr "ชุดภาษา (ส่วนเสริม)"
#: src/olympia/constants/base.py
msgid "Plugins"
@ -3685,7 +3685,7 @@ msgstr "ทุกแพลตฟอร์ม"
#: src/olympia/constants/platforms.py
msgid "Linux"
msgstr ""
msgstr "Linux"
#: src/olympia/constants/platforms.py
msgid "Mac OS X"
@ -3697,15 +3697,15 @@ msgstr "BSD"
#: src/olympia/constants/platforms.py
msgid "Windows"
msgstr ""
msgstr "Windows"
#: src/olympia/constants/platforms.py
msgid "Solaris"
msgstr ""
msgstr "Solaris"
#: src/olympia/constants/platforms.py
msgid "Android"
msgstr ""
msgstr "Android"
#: src/olympia/constants/webext_permissions.py
msgid "Requests that the extension be granted permissions according to the activeTab specification."
@ -4576,7 +4576,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/details.html src/olympia/users/forms.py src/olympia/users/templates/users/edit.html src/olympia/users/templates/users/vcard.html
msgid "Homepage"
msgstr ""
msgstr "หน้าแรก"
#: src/olympia/devhub/templates/devhub/addons/edit/details.html
msgid ""
@ -4642,7 +4642,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/technical.html
msgid "View Source?"
msgstr ""
msgstr "ดูต้นฉบับ?"
#: src/olympia/devhub/templates/devhub/addons/edit/technical.html
msgid "Whether the source of your add-on can be displayed in our online viewer. It is only relevant for listed add-ons."
@ -4674,7 +4674,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/technical.html
msgid "Upgrade SDK?"
msgstr ""
msgstr "อัปเกรด SDK?"
#: src/olympia/devhub/templates/devhub/addons/edit/technical.html
msgid "If selected, we will try to automatically upgrade your add-on when a new version of the SDK is released. It is only relevant for listed add-ons."
@ -5348,7 +5348,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/includes/addons_edit_nav.html
msgid "View All"
msgstr ""
msgstr "ดูทั้งหมด"
#: src/olympia/devhub/templates/devhub/includes/addons_edit_nav.html
msgid "View Statistics Dashboard"
@ -5430,7 +5430,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/includes/version_file.html
msgid "Undo"
msgstr ""
msgstr "เลิกทำ"
#. {0} is an application, like Firefox.
#: src/olympia/devhub/templates/devhub/new-landing/index.html src/olympia/search/views.py src/olympia/templates/base.html src/olympia/templates/impala/base.html
@ -5445,11 +5445,11 @@ msgstr "รุ่นล่าสุด:"
#: src/olympia/devhub/templates/devhub/new-landing/components/blog_posts.html
msgid "Read more in our Blog"
msgstr ""
msgstr "อ่านเพิ่มเติมในบล็อกของเรา"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
msgid "Resources"
msgstr ""
msgstr "ทรัพยากร"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
@ -5463,15 +5463,15 @@ msgstr "ความคิดเห็น"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html src/olympia/templates/footer.html
msgid "About"
msgstr ""
msgstr "เกี่ยวกับ"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html src/olympia/templates/footer.html
msgid "Forum"
msgstr ""
msgstr "กระดานสนทนา"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html src/olympia/devhub/templates/devhub/new-landing/components/navigation.html src/olympia/templates/footer.html
msgid "Blog"
msgstr ""
msgstr "บล็อก"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
@ -5485,7 +5485,7 @@ msgstr "บทวิจารณ์:"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html src/olympia/templates/footer.html
msgid "FAQ"
msgstr ""
msgstr "คำถามที่พบบ่อย"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
@ -5939,11 +5939,11 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/personas/edit.html
msgid "Header"
msgstr ""
msgstr "ส่วนหัว"
#: src/olympia/devhub/templates/devhub/personas/edit.html
msgid "Footer"
msgstr ""
msgstr "ส่วนท้าย"
#: src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "Select colors for your Theme."
@ -5959,7 +5959,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "Background"
msgstr ""
msgstr "พื้นหลัง"
#: src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "This is the color of the tabs."
@ -6106,7 +6106,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/versions/edit.html
msgid "Source code"
msgstr ""
msgstr "โค้ดต้นฉบับ"
#: src/olympia/devhub/templates/devhub/versions/edit.html
msgid "If your add-on contain binary or obfuscated code, make the source available here for reviewers."

Просмотреть файл

@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: REMORA 0.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-02-09 11:33+0000\n"
"PO-Revision-Date: 2017-02-04 13:45+0000\n"
"PO-Revision-Date: 2017-02-17 18:28+0000\n"
"Last-Translator: Selim Şumlu <selim@sum.lu>\n"
"Language-Team: Turkish (Türkçe)\n"
"Language: tr\n"
@ -169,8 +169,8 @@ msgstr "Kullanıcılara her zaman Eklenti Yöneticisi'nde sorulacak (Firefox 4 v
msgid "Listed"
msgstr "Listelenen"
# %1 is an add-on name.
#. l10n: {0} is the addon name
# %1 is an add-on name.
#: src/olympia/addons/views.py
msgid "Contribution for {0}"
msgstr "{0} için bağış"
@ -236,9 +236,9 @@ msgstr "Bu eklentinin geliştiricisi, <a href=\"%(charity_url)s\">%(charity_name
msgid "Contribute"
msgstr "Bağışta Bulun"
#. Click Contribute button OR Install button
# This is a separator between the graphical [contribute] button and the
# graphical [download now] button
#. Click Contribute button OR Install button
#: src/olympia/addons/templates/addons/contribution.html src/olympia/addons/templates/addons/impala/contribution.html src/olympia/addons/templates/addons/report_abuse.html
#: src/olympia/devhub/templates/devhub/addons/ajax_compat_update.html src/olympia/devhub/templates/devhub/addons/edit/admin.html src/olympia/devhub/templates/devhub/addons/edit/basic.html
#: src/olympia/devhub/templates/devhub/addons/edit/details.html src/olympia/devhub/templates/devhub/addons/edit/support.html src/olympia/devhub/templates/devhub/addons/edit/technical.html
@ -282,17 +282,14 @@ msgstr "<strong>%(addon_name)s</strong> eklentisinin süren gelişimini destekle
#: src/olympia/addons/templates/addons/contributions_lightbox.html
#, python-format
msgid ""
"To show your support for <b>%(addon_name)s</b>, the developer asks that you make a donation to the <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</a>."
msgid "To show your support for <b>%(addon_name)s</b>, the developer asks that you make a donation to the <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</a>."
msgstr ""
"<b>%(addon_name)s</b> eklentisine desteğinizi göstermeniz için, geliştirici, <a href=\"%(paypal_url)s\">PayPal</a> üzerinden <a href=\"%(charity_url)s\">%(charity_name)s</a> kurumuna bağışta "
"bulunmanızı rica ediyor."
#: src/olympia/addons/templates/addons/contributions_lightbox.html
#, python-format
msgid ""
"To show your support for <b>%(addon_name)s</b>, the developer asks that you make a small contribution to <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</"
"a>."
msgid "To show your support for <b>%(addon_name)s</b>, the developer asks that you make a small contribution to <a href=\"%(charity_url)s\">%(charity_name)s</a> through <a href=\"%(paypal_url)s\">PayPal</a>."
msgstr ""
"<b>%(addon_name)s</b> eklentisine desteğinizi göstermeniz için, geliştirici, <a href=\"%(paypal_url)s\">PayPal</a> üzerinden <a href=\"%(charity_url)s\">%(charity_name)s</a> kurumuna küçük bir "
"bağışta bulunmanızı rica ediyor."
@ -487,10 +484,10 @@ msgstr "Burda bir şey yok! Geliştirici herhangi bir ayrıntı vermemiş."
msgid "Version Notes"
msgstr "Sürüm Notları"
# 76%
# 100%
#. {0} is the name of the add-on.
#. {0} is the name of the add-on
# 76%
# 100%
#: src/olympia/addons/templates/addons/eula.html src/olympia/legacy_discovery/templates/legacy_discovery/addons/eula.html
msgid "End-User License Agreement for {0}"
msgstr "{0} Son Kullanıcı Lisans Anlaşması"
@ -778,8 +775,8 @@ msgstr "Bu Persona %(app)s %(new_version)s gerektiriyor. Siz şu anda %(app)s {o
#: src/olympia/addons/templates/addons/popups.html
msgid ""
"<strong>Caution:</strong> This add-on has not been reviewed by Mozilla and can't be installed on release versions of Firefox 43 and above. Be careful when installing third-party software that "
"might harm your computer."
"<strong>Caution:</strong> This add-on has not been reviewed by Mozilla and can't be installed on release versions of Firefox 43 and above. Be careful when installing third-party software that might"
" harm your computer."
msgstr ""
"<strong>Dikkat:</strong> Bu eklenti Mozilla tarafından incelenmemiştir. Bu yüzden Firefox 43 ve üstü sürümlere yüklenemez. Bilgisayarınıza zarar verebilecek üçüncü parti yazılımları kurarken "
"dikkatli olun."
@ -1099,8 +1096,8 @@ msgstr "kapat"
msgid "Thank you for installing {0}"
msgstr "{0} eklentisini kurduğunuz için teşekkürler"
# %1 is an add-on name.
#. {0} is an add-on name.
# %1 is an add-on name.
#: src/olympia/addons/templates/addons/impala/developers.html
msgid "Meet the {0} Developer"
msgstr "{0} Geliştiricisiyle Tanışın"
@ -1802,8 +1799,8 @@ msgstr "Bulunamadı"
#, fuzzy, python-format
msgid ""
"<h1>We're sorry, but we can't find what you're looking for.</h1> <p> The page or file you requested wasn't found on our site. It's possible that you clicked a link that's out of date, or typed in "
"the address incorrectly. </p> <ul> <li>If you typed in the address, please double check the spelling.</li> <li> If you followed a link from somewhere, please <a href=\"https://github.com/mozilla/"
"addons-server/issues/new/\">file an issue</a>. Tell us where you came from and what you were looking for, and we'll do our best to fix it. </li> </ul> <p>Or you can just jump over to some of the "
"the address incorrectly. </p> <ul> <li>If you typed in the address, please double check the spelling.</li> <li> If you followed a link from somewhere, please <a href=\"https://github.com/mozilla"
"/addons-server/issues/new/\">file an issue</a>. Tell us where you came from and what you were looking for, and we'll do our best to fix it. </li> </ul> <p>Or you can just jump over to some of the "
"popular pages on our website.</p> <ul> <li>Are you interested in a <a href=\"%(rec)s\">list of featured add-ons</a>?</li> <li> Do you want to <a href=\"%(search)s\">search for add-ons</a>? You may "
"go to the <a href=\"%(search)s\">search page</a> or just use the search field above. </li> <li> If you prefer to start over, just go to the <a href=\"%(home)s\">add-ons front page</a>. </li> </ul>"
msgstr ""
@ -2336,8 +2333,8 @@ msgstr ""
#: src/olympia/bandwagon/templates/bandwagon/collection_detail.html
msgid ""
"Add-ons that you mark as favorites using the <b>Add to Favorites</b> feature appear below. This collection is currently <b>private</b>, which means only you can see it. If you would like everyone "
"to be able to see your favorites, click the button below to make it public."
"Add-ons that you mark as favorites using the <b>Add to Favorites</b> feature appear below. This collection is currently <b>private</b>, which means only you can see it. If you would like everyone to"
" be able to see your favorites, click the button below to make it public."
msgstr ""
#: src/olympia/bandwagon/templates/bandwagon/collection_detail.html
@ -2565,7 +2562,7 @@ msgstr "Koleksiyon Oluştur"
#: src/olympia/bandwagon/templates/bandwagon/impala/collection_sidebar.html src/olympia/bandwagon/templates/bandwagon/includes/collection_sidebar.html
msgid "Collections make it easy to keep track of favorite add-ons and share your perfectly customized browser with others."
msgstr ""
msgstr "Koleksiyonlar, sevilen eklentileri takip etmenizi ve kusursuzca özelleştirdiğiniz tarayıcınızı diğer kişiler ile paylaşmanızı kolaylaştırır."
#: src/olympia/bandwagon/templates/bandwagon/includes/addedit.html
msgid "Collection Icon"
@ -3001,8 +2998,8 @@ msgstr "Eklenti Uyumluluk Raporları"
#: src/olympia/compat/templates/compat/reporter.html src/olympia/compat/templates/compat/reporter_detail.html
#, python-format
msgid ""
"<p> Reports submitted to us through the <a href=\"%(url_)s\">Add-on Compatibility Reporter</a> are collected here for developers to view. These reports help us determine which add-ons will need "
"help supporting an upcoming Firefox version. </p>"
"<p> Reports submitted to us through the <a href=\"%(url_)s\">Add-on Compatibility Reporter</a> are collected here for developers to view. These reports help us determine which add-ons will need help"
" supporting an upcoming Firefox version. </p>"
msgstr ""
"<p> <a href=\"%(url_)s\">Eklenti Uyumluluk Bildirici</a> aracılığıyla bize gönderilen raporlar geliştiricilerin değerlendirmesi için toplanır. Bu raporlar bize hangi eklentilerin gelecek Firefox "
"sürümünü destekleyeceğine karar vermemize yardımcı olur. </p>"
@ -3650,7 +3647,7 @@ msgstr "Fotoğraflar ve Medya"
#: src/olympia/constants/categories.py
msgid "RSS, News and Blogging"
msgstr ""
msgstr "RSS, Haberler ve Bloglar"
#: src/olympia/constants/categories.py
msgid "Site-specific"
@ -3818,9 +3815,8 @@ msgid "Slug incorrect."
msgstr ""
#: src/olympia/devhub/forms.py
#, fuzzy
msgid "Invalid JSON object"
msgstr "Geçersiz JWT jetonu."
msgstr "Geçersiz JSON nesnesi"
#: src/olympia/devhub/forms.py
msgid "Message not eligible for annotation"
@ -3843,21 +3839,18 @@ msgid "License text is required when choosing Other."
msgstr "Diğer seçeneği seçildiğinde sözleşme metni girilmesi gerekir."
#: src/olympia/devhub/forms.py
#, fuzzy
msgid "This add-on has an End-User License Agreement"
msgstr "Son Kullanıcı İzin Anlaşmasına Bak"
msgstr "Bu sözleşmenin bir Son Kullanıcı İzin Sözleşmesi var"
#: src/olympia/devhub/forms.py
#, fuzzy
msgid "Please specify your add-on's End-User License Agreement:"
msgstr "Son Kullanıcı İzin Anlaşmasına Bak"
msgstr "Eklentinizin Son Kullanıcı İzin Sözleşmesi'ni belirtin:"
# 89%
# 100%
#: src/olympia/devhub/forms.py
#, fuzzy
msgid "This add-on has a Privacy Policy"
msgstr "Gizlilik Politikasını İncele"
msgstr "Bu eklentinin Gizlilik Kuralları var"
#: src/olympia/devhub/forms.py
msgid "Please specify your add-on's Privacy Policy:"
@ -3998,8 +3991,7 @@ msgid ""
msgstr ""
#: src/olympia/devhub/forms.py
msgid ""
"On your own. <span class=\"helptext\">Your submission will be immediately signed for self-distribution. Updates should be handled by you via an updateURL or external application updates.</span>"
msgid "On your own. <span class=\"helptext\">Your submission will be immediately signed for self-distribution. Updates should be handled by you via an updateURL or external application updates.</span>"
msgstr ""
#: src/olympia/devhub/helpers.py
@ -4100,11 +4092,11 @@ msgstr "Eklenti silindi."
#: src/olympia/devhub/views.py
msgid "URL name was incorrect. Theme was not deleted."
msgstr ""
msgstr "URL adı yanlış. Tema silinmedi."
#: src/olympia/devhub/views.py
msgid "URL name was incorrect. Add-on was not deleted."
msgstr ""
msgstr "URL adı yanlış. Eklenti silinmedi."
#: src/olympia/devhub/views.py
msgid "An author has been added to your add-on"
@ -4157,7 +4149,7 @@ msgstr "Bu isim kullanılamaz."
#: src/olympia/devhub/views.py
#, python-format
msgid "Images cannot be larger than %dKB."
msgstr ""
msgstr "Resimler %d KB'den büyük olamaz."
#. L10n: {0} is an image width (in pixels), {1} is a height.
#: src/olympia/devhub/views.py
@ -4258,7 +4250,7 @@ msgstr "Arama Sonuçları"
#: src/olympia/devhub/templates/devhub/devhub_search.html
msgid "Please enter some search terms."
msgstr ""
msgstr "Lütfen bazı arama terimleri girin."
#: src/olympia/devhub/templates/devhub/devhub_search.html
msgid "Loading results&hellip;"
@ -4288,9 +4280,9 @@ msgstr "Koleksiyonlar hakkında bilgi edinin"
#: src/olympia/devhub/templates/devhub/index-legacy.html
msgid ""
"Add-ons let millions of Firefox users enhance and customize their browsing experience. If you're a Web developer and know <a href=\"https://developer.mozilla.org/docs/Web/HTML\">HTML</a>, <a href="
"\"https://developer.mozilla.org/docs/Web/JavaScript\">JavaScript</a>, and <a href=\"https://developer.mozilla.org/docs/Web/CSS\">CSS</a>, you already have all the necessary skills to make a great "
"add-on."
"Add-ons let millions of Firefox users enhance and customize their browsing experience. If you're a Web developer and know <a href=\"https://developer.mozilla.org/docs/Web/HTML\">HTML</a>, <a "
"href=\"https://developer.mozilla.org/docs/Web/JavaScript\">JavaScript</a>, and <a href=\"https://developer.mozilla.org/docs/Web/CSS\">CSS</a>, you already have all the necessary skills to make a "
"great add-on."
msgstr ""
#: src/olympia/devhub/templates/devhub/index-legacy.html
@ -4493,9 +4485,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/ajax_compat_error.html
#, python-format
msgid ""
"<a href=\"#\" class=\"button compat-update\" data-updateurl=\"%(update_url)s\">Update Compatibility</a> <a href=\"%(version_url)s\" class=\"button\">Upload New Version</a> or <a href=\"#\" class="
"\"close\">Ignore</a>"
msgid "<a href=\"#\" class=\"button compat-update\" data-updateurl=\"%(update_url)s\">Update Compatibility</a> <a href=\"%(version_url)s\" class=\"button\">Upload New Version</a> or <a href=\"#\" class=\"close\">Ignore</a>"
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/ajax_compat_status.html
@ -4522,7 +4512,7 @@ msgstr "Desteklenen sürümler"
#: src/olympia/devhub/templates/devhub/addons/ajax_compat_update.html src/olympia/devhub/templates/devhub/versions/edit.html
msgid "Add Another Application&hellip;"
msgstr ""
msgstr "Başka bir uygulama ekle &hellip;"
#: src/olympia/devhub/templates/devhub/addons/ajax_compat_update.html src/olympia/devhub/templates/devhub/addons/listing/delete_form.html src/olympia/devhub/templates/devhub/addons/owner.html
#: src/olympia/devhub/templates/devhub/versions/list.html src/olympia/templates/impala/base.html
@ -4667,8 +4657,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/basic.html
#, python-format
msgid ""
"Categories cannot be changed while your add-on is featured for this application. Please email <a href=\"mailto:%(email)s\">%(email)s</a> if there is a reason you need to modify your categories."
msgid "Categories cannot be changed while your add-on is featured for this application. Please email <a href=\"mailto:%(email)s\">%(email)s</a> if there is a reason you need to modify your categories."
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/basic.html
@ -4740,8 +4729,8 @@ msgstr "{0} Eklenti Bilgileri"
#: src/olympia/devhub/templates/devhub/addons/edit/support.html
msgid ""
"If you wish to display an e-mail address for support inquiries, enter it here. If you have different addresses for each language multiple translations of this field can be added. It is only "
"relevant for listed add-ons."
"If you wish to display an e-mail address for support inquiries, enter it here. If you have different addresses for each language multiple translations of this field can be added. It is only relevant"
" for listed add-ons."
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/edit/support.html
@ -5123,10 +5112,9 @@ msgstr "<b>Sıra:</b> %(pos)s / %(total)s"
#. {0} is the number of active users.
#: src/olympia/devhub/templates/devhub/addons/listing/macros.html
#, fuzzy
msgid "<strong>{0}</strong> active user"
msgid_plural "<strong>{0}</strong> active users"
msgstr[0] "<strong>{0}</strong> kullanıcı"
msgstr[0] "<strong>{0}</strong> etkin kullanıcı"
#: src/olympia/devhub/templates/devhub/addons/submit/base.html
#, fuzzy
@ -5172,9 +5160,8 @@ msgid "Check this option if your add-on is experimental or otherwise not ready f
msgstr ""
#: src/olympia/devhub/templates/devhub/addons/submit/describe.html
#, fuzzy
msgid "Support email:"
msgstr "E-posta desteği"
msgstr "Destek e-postası:"
#: src/olympia/devhub/templates/devhub/addons/submit/describe.html
#, fuzzy
@ -5391,8 +5378,8 @@ msgstr "JWT sırrı"
#: src/olympia/devhub/templates/devhub/api/key.html
#, python-format
msgid ""
"To make API requests, send a <a href=\"%(jwt_url)s\">JSON Web Token (JWT)</a> as the authorization header. You'll need to generate a JWT for every request as explained in the <a href=\"%(docs_url)s"
"\">API documentation</a>."
"To make API requests, send a <a href=\"%(jwt_url)s\">JSON Web Token (JWT)</a> as the authorization header. You'll need to generate a JWT for every request as explained in the <a "
"href=\"%(docs_url)s\">API documentation</a>."
msgstr ""
#: src/olympia/devhub/templates/devhub/api/key.html
@ -5544,9 +5531,8 @@ msgid "{0} success reports"
msgstr "{0} başarı raporu"
#: src/olympia/devhub/templates/devhub/includes/blog_posts.html
#, fuzzy
msgid "Developer News"
msgstr "Geliştirici"
msgstr "Geliştirici Haberleri"
#: src/olympia/devhub/templates/devhub/includes/blog_posts.html
msgid "View the blog &#9658;"
@ -5620,7 +5606,7 @@ msgstr "Eklentiler"
#: src/olympia/devhub/templates/devhub/new-landing/components/blog_posts.html
msgid "Latest News"
msgstr ""
msgstr "Son Haberler"
#: src/olympia/devhub/templates/devhub/new-landing/components/blog_posts.html
msgid "Read more in our Blog"
@ -5656,9 +5642,8 @@ msgid "Blog"
msgstr "Blog"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
msgid "Contact Us"
msgstr "Kişiler"
msgstr "Bize Ulaşın"
#: src/olympia/devhub/templates/devhub/new-landing/components/footer.html
#, fuzzy
@ -5828,14 +5813,12 @@ msgid "Docs"
msgstr "Belgeler"
#: src/olympia/devhub/templates/devhub/new-landing/components/navigation.html
#, fuzzy
msgid "Sign Out"
msgstr "Giriş yap"
msgstr "Çıkış yap"
#: src/olympia/devhub/templates/devhub/new-landing/components/navigation.html
#, fuzzy
msgid "User Photo"
msgstr "Kullanıcı fotoğrafını sil"
msgstr "Kullanıcı Fotoğrafı"
#: src/olympia/devhub/templates/devhub/new-landing/components/navigation.html
#, fuzzy, python-format
@ -5850,8 +5833,8 @@ msgstr "Firefox için kullanımı kolay temalar"
#: src/olympia/devhub/templates/devhub/new-landing/components/overview.html
#, python-format
msgid ""
"Add-ons let millions of Firefox users enhance their browsing experience. If you know <a href=\"%(html_link)s\">HTML</a>, <a href=\"%(js_link)s\">JavaScript</a>, and <a href=\"%(css_link)s\">CSS</"
"a>, you already have all the necessary skills to make a great add-on."
"Add-ons let millions of Firefox users enhance their browsing experience. If you know <a href=\"%(html_link)s\">HTML</a>, <a href=\"%(js_link)s\">JavaScript</a>, and <a href=\"%(css_link)s\">CSS</a>,"
" you already have all the necessary skills to make a great add-on."
msgstr ""
#: src/olympia/devhub/templates/devhub/new-landing/components/overview.html
@ -5997,7 +5980,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/payments/voluntary.html
msgid "Sign up for PayPal"
msgstr ""
msgstr "PayPal'a kaydolun"
#: src/olympia/devhub/templates/devhub/payments/voluntary.html
#, fuzzy
@ -6006,7 +5989,7 @@ msgstr "Önerilen tek seferlik bağış miktarı: {0}"
#: src/olympia/devhub/templates/devhub/payments/voluntary.html
msgid "(Example: 3.99)"
msgstr ""
msgstr "(Örnek: 3.99)"
#: src/olympia/devhub/templates/devhub/payments/voluntary.html
msgid "When should users be asked for contributions?"
@ -6102,8 +6085,8 @@ msgstr "Lisans sahibi; bu çalışmayı temel alacak çalışmaların yalnızca
#: src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "The licensor permits others to copy, distribute and transmit only unaltered copies of the work — not derivative works based on it."
msgstr ""
"Lisans sahibi; bu çalışmanın yalnızca değiştirilmemiş halinin başkaları tarafından kopyalanmasına, dağıtılmasına, sergilenmesine ve uygulanmasına izin veriyor; çalışmayı temel alan başka "
"çalışmalara izin vermiyor."
"Lisans sahibi; bu çalışmanın yalnızca değiştirilmemiş halinin başkaları tarafından kopyalanmasına, dağıtılmasına, sergilenmesine ve uygulanmasına izin veriyor; çalışmayı temel alan başka çalışmalara"
" izin vermiyor."
#: src/olympia/devhub/templates/devhub/personas/edit.html src/olympia/devhub/templates/devhub/personas/submit.html
msgid "Your Theme will be released under the following license:"
@ -6413,7 +6396,7 @@ msgstr "Simge"
#: src/olympia/devhub/templates/devhub/versions/list.html
msgid "Leave a reply"
msgstr ""
msgstr "Yanıt yaz"
#: src/olympia/devhub/templates/devhub/versions/list.html src/olympia/reviews/templates/reviews/reply.html
msgid "Reply"
@ -6518,7 +6501,7 @@ msgstr ""
#: src/olympia/devhub/templates/devhub/versions/list.html
msgid "Are you sure you wish to hide your add-on?"
msgstr ""
msgstr "Eklentinizi gizlemek istediğinizden emin misiniz?"
#: src/olympia/devhub/templates/devhub/versions/list.html
msgid "Canceling your review request will mark your add-on incomplete, and any versions awaiting review will be disabled."
@ -6711,10 +6694,9 @@ msgid "[status:%s]"
msgstr "[durum:%s]"
#: src/olympia/editors/helpers.py
#, fuzzy
msgid "New Add-on ({0})"
msgid_plural "New Add-ons ({0})"
msgstr[0] "Eklenti İnceleme Süreci"
msgstr[0] "Yeni Eklentiler ({0})"
#: src/olympia/editors/helpers.py
#, fuzzy
@ -8042,7 +8024,7 @@ msgstr "Video"
#: src/olympia/legacy_discovery/templates/legacy_discovery/pane.html
msgid "While the video plays, the add-ons being mentioned will appear here."
msgstr ""
msgstr "Video oynarken ilgili eklentiler burada görünecektir."
#. {0} is the user's login name.
#: src/olympia/legacy_discovery/templates/legacy_discovery/pane_account.html
@ -8053,7 +8035,7 @@ msgstr "{0} koleksiyonunu düzenle"
#: src/olympia/legacy_discovery/templates/legacy_discovery/pane_account.html
#, python-format
msgid "Thanks for using %(app)s and supporting <a href=\"%(url)s\">Mozilla's mission</a>!"
msgstr ""
msgstr "%(app)s kullanarak <a href=\"%(url)s\">Mozilla'nın misyonunu</a> desteklediğiniz için teşekkürler!"
#: src/olympia/legacy_discovery/templates/legacy_discovery/pane_account.html
msgid "Add-ons downloaded:"
@ -8095,7 +8077,7 @@ msgstr "Birleşik Arap Emirlikleri Dirhemi"
#: src/olympia/lib/constants.py
msgid "Afghanistan Afghani"
msgstr ""
msgstr "Afganistan Afganisi"
#: src/olympia/lib/constants.py
msgid "Albania Lek"
@ -8123,11 +8105,11 @@ msgstr "Avustralya Doları"
#: src/olympia/lib/constants.py
msgid "Aruba Guilder"
msgstr ""
msgstr "Aruba Florini"
#: src/olympia/lib/constants.py
msgid "Azerbaijan New Manat"
msgstr ""
msgstr "Azerbaycan Manatı"
#: src/olympia/lib/constants.py
msgid "Bosnia and Herzegovina Convertible Marka"
@ -8195,7 +8177,7 @@ msgstr "Kanada Doları"
#: src/olympia/lib/constants.py
msgid "Congo/Kinshasa Franc"
msgstr ""
msgstr "Kongo/Kinşasa Frangı"
#: src/olympia/lib/constants.py
msgid "Switzerland Franc"
@ -8272,15 +8254,15 @@ msgstr "Fiji Doları"
#: src/olympia/lib/constants.py
msgid "Falkland Islands (Malvinas) Pound"
msgstr ""
msgstr "Falkland Adaları (Malvinas) Poundu"
#: src/olympia/lib/constants.py
msgid "United Kingdom Pound"
msgstr ""
msgstr "İngiliz Sterlini"
#: src/olympia/lib/constants.py
msgid "Georgia Lari"
msgstr ""
msgstr "Gürcistan Larisi"
#: src/olympia/lib/constants.py
msgid "Guernsey Pound"
@ -8288,15 +8270,15 @@ msgstr ""
#: src/olympia/lib/constants.py
msgid "Ghana Cedi"
msgstr ""
msgstr "Gana Cedisi"
#: src/olympia/lib/constants.py
msgid "Gibraltar Pound"
msgstr ""
msgstr "Cebelitarık Poundu"
#: src/olympia/lib/constants.py
msgid "Gambia Dalasi"
msgstr ""
msgstr "Gambiya Dalasi"
#: src/olympia/lib/constants.py
msgid "Guinea Franc"
@ -8336,7 +8318,7 @@ msgstr "Endonezya Rupisi"
#: src/olympia/lib/constants.py
msgid "Israel Shekel"
msgstr ""
msgstr "İsrail Şekeli"
#: src/olympia/lib/constants.py
msgid "Isle of Man Pound"
@ -8818,8 +8800,7 @@ msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
#, python-format
msgid ""
"Help improve this website. It's open source, and you can file bugs and submit patches. <a href=\"%(url)s\"> GitHub</a> contains all of our current bugs, legacy bugs can still be found in Bugzilla."
msgid "Help improve this website. It's open source, and you can file bugs and submit patches. <a href=\"%(url)s\"> GitHub</a> contains all of our current bugs, legacy bugs can still be found in Bugzilla."
msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
@ -8846,9 +8827,7 @@ msgstr "Geliştiriciye soru sorun"
#: src/olympia/pages/templates/pages/about.lhtml
#, python-format
msgid ""
"A good place to start is our <a href=\"%(faq_url)s\"><abbr title=\"Frequently Asked Questions\">FAQ</abbr></a>. If you don't find an answer there, you can <a href=\"%(forum_url)s\"> ask on our "
"forums</a>."
msgid "A good place to start is our <a href=\"%(faq_url)s\"><abbr title=\"Frequently Asked Questions\">FAQ</abbr></a>. If you don't find an answer there, you can <a href=\"%(forum_url)s\"> ask on our forums</a>."
msgstr ""
#: src/olympia/pages/templates/pages/about.lhtml
@ -8863,8 +8842,8 @@ msgstr "Bu sitede kim çalışıyor?"
#: src/olympia/pages/templates/pages/about.lhtml
#, python-format
msgid ""
"Over the years, many people have contributed to this website, including both volunteers from the community and a dedicated AMO team. A list of significant contributors can be found on our <a href="
"\"%(url)s\"> Site Credits</a> page."
"Over the years, many people have contributed to this website, including both volunteers from the community and a dedicated AMO team. A list of significant contributors can be found on our <a "
"href=\"%(url)s\"> Site Credits</a> page."
msgstr ""
#: src/olympia/pages/templates/pages/acr_firstrun.html
@ -9101,8 +9080,8 @@ msgstr "\"install.rdf\" dosyası ne için kullanılır?"
#, python-format
msgid ""
"This file, called an <a href=\"%(url)s\">Install Manifest</a>, is used by Add-on Manager-enabled XUL applications to determine information about an add-on as it is being installed. It contains "
"metadata identifying the add-on, providing information about who created it, where more information can be found about it, which versions of what applications it is compatible with, how it should "
"be updated, and so on. The format of the Install Manifest is RDF/XML."
"metadata identifying the add-on, providing information about who created it, where more information can be found about it, which versions of what applications it is compatible with, how it should be"
" updated, and so on. The format of the Install Manifest is RDF/XML."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9150,9 +9129,9 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
msgid ""
"To ensure compatibility with the latest Mozilla software, it's important to download updates as they become available and test your add-on to ensure that it is still functioning as expected. In "
"many cases, the latest version of Mozilla software may be a beta release. Since these releases at times introduce architectural changes that may impact the functionality of your add-on, it's "
"important to be actively involved in the beta process to ensure that your add-on users are not negatively impacted upon final release of Mozilla software."
"To ensure compatibility with the latest Mozilla software, it's important to download updates as they become available and test your add-on to ensure that it is still functioning as expected. In many"
" cases, the latest version of Mozilla software may be a beta release. Since these releases at times introduce architectural changes that may impact the functionality of your add-on, it's important "
"to be actively involved in the beta process to ensure that your add-on users are not negatively impacted upon final release of Mozilla software."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9162,8 +9141,8 @@ msgstr "Eklentimin başarımını nasıl artırabilirim?"
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"Poorly written extensions can have a severe impact on the browsing experience, including on the overall performance of Firefox itself. The following page contains many good <a href=\"%(url)s"
"\">guides</a> that help you improve performance, whether you're developing core Mozilla code or an add-on."
"Poorly written extensions can have a severe impact on the browsing experience, including on the overall performance of Firefox itself. The following page contains many good <a "
"href=\"%(url)s\">guides</a> that help you improve performance, whether you're developing core Mozilla code or an add-on."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9239,8 +9218,8 @@ msgstr "Eklentimi geliştirmek için tutabileceğim 3. şahıs geliştiriciler v
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"Yes. You may find 3rd party developers via the <a href=\"%(forum_url)s\">Add-ons forum</a>, <a href=\"%(list_url)s\">mozilla.jobs list</a>, <a href=\"%(mz_url)s\">mozillaZine forums</a> or <a href="
"\"%(wiki_url)s\">the Mozilla Wiki</a>. Please note that Mozilla does not offer developer recommendations."
"Yes. You may find 3rd party developers via the <a href=\"%(forum_url)s\">Add-ons forum</a>, <a href=\"%(list_url)s\">mozilla.jobs list</a>, <a href=\"%(mz_url)s\">mozillaZine forums</a> or <a "
"href=\"%(wiki_url)s\">the Mozilla Wiki</a>. Please note that Mozilla does not offer developer recommendations."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9331,8 +9310,8 @@ msgstr "Eklentim için hangi kategoriyi seçmeliyim?"
#: src/olympia/pages/templates/pages/dev_faq.html
msgid ""
"The choice of category is dependent on what type of audience you are targeting and the functionality of your add-on. If you're unsure of which category your add-on falls into, please choose \"Other"
"\". The AMO team may re-categorize your add-on if it's determined that it's better suited in a different category."
"The choice of category is dependent on what type of audience you are targeting and the functionality of your add-on. If you're unsure of which category your add-on falls into, please choose "
"\"Other\". The AMO team may re-categorize your add-on if it's determined that it's better suited in a different category."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9371,9 +9350,9 @@ msgstr "Bu eklentinin %(cnt)s incelemesinin tümünü göster"
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"Add-ons are reviewed by the Add-on Reviewers, a group of talented developers that volunteer to help the Mozilla project by reviewing add-ons to ensure a stable and safe experience for Mozilla "
"users. When communicating with reviewer, please be courteous, patient and respectful as they are working hard to ensure that your add-on is set up correctly and follows the guidelines outlined in "
"the <a href=\"%(url)s\">Add-on Review Guide</a>."
"Add-ons are reviewed by the Add-on Reviewers, a group of talented developers that volunteer to help the Mozilla project by reviewing add-ons to ensure a stable and safe experience for Mozilla users."
" When communicating with reviewer, please be courteous, patient and respectful as they are working hard to ensure that your add-on is set up correctly and follows the guidelines outlined in the <a "
"href=\"%(url)s\">Add-on Review Guide</a>."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9413,8 +9392,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
#, python-format
msgid ""
"This is why it's very important to read the <a href=\"%(g_url)s\">Add-on Review Guide</a> to ensure that your add-on is setup as expected. It's also a good idea to read the blog post, <a href="
"\"%(blog_url)s\">Successfully Getting your Add-on Reviewed</a> which provides excellent insight into ensuring a smooth review of your add-on."
"This is why it's very important to read the <a href=\"%(g_url)s\">Add-on Review Guide</a> to ensure that your add-on is setup as expected. It's also a good idea to read the blog post, <a "
"href=\"%(blog_url)s\">Successfully Getting your Add-on Reviewed</a> which provides excellent insight into ensuring a smooth review of your add-on."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9502,10 +9481,10 @@ msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
msgid ""
"Do you need more information about the various open source licenses? Are you confused as to which license you should select? What rights does a specific license grant? While nothing replaces "
"reading the full terms of a license, below are some sites that contain information about some of the key open source licenses that may help you sort out the differences between them. These sites "
"are being provided solely for your convenience and as a reference for your personal use. These resources do not constitute legal advice nor should they be used in lieu of such advice. Mozilla "
"neither guarantees nor is responsible for the content of these sites or your reliance on such content."
"Do you need more information about the various open source licenses? Are you confused as to which license you should select? What rights does a specific license grant? While nothing replaces reading"
" the full terms of a license, below are some sites that contain information about some of the key open source licenses that may help you sort out the differences between them. These sites are being "
"provided solely for your convenience and as a reference for your personal use. These resources do not constitute legal advice nor should they be used in lieu of such advice. Mozilla neither "
"guarantees nor is responsible for the content of these sites or your reliance on such content."
msgstr ""
#: src/olympia/pages/templates/pages/dev_faq.html
@ -9565,9 +9544,10 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"Add-ons listed in this gallery only work with Mozilla-based applications, such as <a href=\"%(getfirefox_url)s\">Firefox</a>, <a href=\"%(getmobile_url)s\">Firefox Mobile</a>, <a href="
"\"%(getseamonkey_url)s\">SeaMonkey</a>, and <a href=\"%(getthunderbird_url)s\">Thunderbird</a>. However, not all add-ons work with each of those applications or every version of those applications. "
"Each add-on specifies which applications and versions it works with, such as Firefox 2.0 - 3.6.*. For Firefox add-ons, the install buttons will indicate whether the add-on is compatible or not."
"Add-ons listed in this gallery only work with Mozilla-based applications, such as <a href=\"%(getfirefox_url)s\">Firefox</a>, <a href=\"%(getmobile_url)s\">Firefox Mobile</a>, <a "
"href=\"%(getseamonkey_url)s\">SeaMonkey</a>, and <a href=\"%(getthunderbird_url)s\">Thunderbird</a>. However, not all add-ons work with each of those applications or every version of those "
"applications. Each add-on specifies which applications and versions it works with, such as Firefox 2.0 - 3.6.*. For Firefox add-ons, the install buttons will indicate whether the add-on is "
"compatible or not."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9630,8 +9610,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"In Firefox, add-ons marked with \"No restart required\" can be installed without restarting. These add-ons have been created using the <a href=\"%(sdk_url)s\">Add-on SDK</a> or <a href="
"\"%(bootstrap_url)s\">bootstrapping</a>. Other add-ons will still require a restart before you can use them."
"In Firefox, add-ons marked with \"No restart required\" can be installed without restarting. These add-ons have been created using the <a href=\"%(sdk_url)s\">Add-on SDK</a> or <a "
"href=\"%(bootstrap_url)s\">bootstrapping</a>. Other add-ons will still require a restart before you can use them."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9642,20 +9622,20 @@ msgstr ""
#, python-format
msgid ""
"Add-ons, unlike plugins, are automatically checked for updates once every day. In Firefox, updates are automatically installed by default. Versions of Firefox prior to 4 (and other applications) "
"will alert you that updates to your add-ons are available. <a href=\"%(plugin_url)s\">Plugins</a> are not currently automatically checked for updates, so be sure to regularly visit the <a href="
"\"%(plugincheck_url)s\">Plugin Check</a> page to stay up-to-date."
"will alert you that updates to your add-ons are available. <a href=\"%(plugin_url)s\">Plugins</a> are not currently automatically checked for updates, so be sure to regularly visit the <a "
"href=\"%(plugincheck_url)s\">Plugin Check</a> page to stay up-to-date."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
msgid "Are add-ons safe to install?"
msgstr ""
msgstr "Eklentileri güvenle yükleyebilir miyim?"
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"Unless clearly marked otherwise, add-ons available from this gallery have been checked and approved by Mozilla's team of editors and are safe to install. We recommend that you only install approved "
"add-ons. If you wish to install unapproved add-ons or add-ons from third-party websites, use caution as these add-ons may harm your computer or violate your privacy. <a href=\"%(learnmore_url)s"
"\">Learn more about our approval process</a>"
"add-ons. If you wish to install unapproved add-ons or add-ons from third-party websites, use caution as these add-ons may harm your computer or violate your privacy. <a "
"href=\"%(learnmore_url)s\">Learn more about our approval process</a>"
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9740,8 +9720,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"While all add-ons publicly available in our gallery are reviewed by an editor, you may receive a direct link to an add-on that hasn't yet been reviewed. Use caution when installing these add-ons, "
"as they could harm your computer or violate your privacy. We recommend that you only install reviewed add-ons. <a href=\"%(url)s\">Learn more about our review process</a></dd>"
"While all add-ons publicly available in our gallery are reviewed by an editor, you may receive a direct link to an add-on that hasn't yet been reviewed. Use caution when installing these add-ons, as"
" they could harm your computer or violate your privacy. We recommend that you only install reviewed add-ons. <a href=\"%(url)s\">Learn more about our review process</a></dd>"
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9769,8 +9749,8 @@ msgstr "Koleksiyon nedir?"
#: src/olympia/pages/templates/pages/faq.html
msgid ""
"Beta add-ons are unreviewed versions which represent the latest work of an add-on author. While different authors have different standards for beta code quality, you should assume that these add-"
"ons are less stable than the general add-on releases."
"Beta add-ons are unreviewed versions which represent the latest work of an add-on author. While different authors have different standards for beta code quality, you should assume that these add-ons"
" are less stable than the general add-on releases."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9814,9 +9794,9 @@ msgstr "Kaynak Kodu Lisansı"
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"The source code used to create an add-on is an exclusive copyright of the add-on author, unless otherwise declared in a source code license. Many add-ons on this site have <a href=\"%(url)s\" lang="
"\"en\">open source licenses</a> that make the source code publicly available for copy and reuse under conditions determined by the author. Most authors choose widely known open source licenses like "
"the GPL or BSD licenses instead of making up their own."
"The source code used to create an add-on is an exclusive copyright of the add-on author, unless otherwise declared in a source code license. Many add-ons on this site have <a href=\"%(url)s\" "
"lang=\"en\">open source licenses</a> that make the source code publicly available for copy and reuse under conditions determined by the author. Most authors choose widely known open source licenses "
"like the GPL or BSD licenses instead of making up their own."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9863,8 +9843,8 @@ msgstr "Koleksiyon nedir?"
#: src/olympia/pages/templates/pages/faq.html
#, python-format
msgid ""
"Mobile add-ons work with <a href=\"%(mobile_url)s\">Firefox for Mobile</a> and add or modify functionality just like desktop add-ons. You can find add-ons that work with Firefox for Mobile in our "
"<a href=\"%(gallery_url)s\">gallery</a>."
"Mobile add-ons work with <a href=\"%(mobile_url)s\">Firefox for Mobile</a> and add or modify functionality just like desktop add-ons. You can find add-ons that work with Firefox for Mobile in our <a"
" href=\"%(gallery_url)s\">gallery</a>."
msgstr ""
#: src/olympia/pages/templates/pages/faq.html
@ -9896,8 +9876,8 @@ msgstr ""
#: src/olympia/pages/templates/pages/review_guide.html
msgid ""
"Add-on reviews are a way for you to share your opinions about the add-ons youve installed and used. Our review moderation team reserves the right to refuse or remove any review that does not "
"comply with these guidelines."
"Add-on reviews are a way for you to share your opinions about the add-ons youve installed and used. Our review moderation team reserves the right to refuse or remove any review that does not comply"
" with these guidelines."
msgstr ""
#: src/olympia/pages/templates/pages/review_guide.html
@ -10082,7 +10062,7 @@ msgstr ""
#: src/olympia/reviews/forms.py
msgid "Skip for now"
msgstr ""
msgstr "Şimdilik geç"
#: src/olympia/reviews/forms.py src/olympia/reviews/templates/reviews/review.html
#, fuzzy
@ -10091,7 +10071,7 @@ msgstr "İnceleme yazın"
#: src/olympia/reviews/models.py
msgid "Other (please specify)"
msgstr ""
msgstr "Diğer (lütfen belirtin)"
#: src/olympia/reviews/views.py
msgid "Thanks; this review has been flagged for editor approval."
@ -10110,10 +10090,10 @@ msgstr "{0} Eklentileri"
msgid ""
"<h2>Keep these tips in mind:</h2> <ul> <li> Write like you're telling a friend about your experience with the add-on. Give specifics and helpful details, such as what features you liked and/or "
"disliked, how easy to use it is, and any disadvantages it has. Avoid generic language such as calling it \"Great\" or \"Bad\" unless you can give reasons why you believe this is so. </li> <li> "
"Please do not post bug reports here. We do not make your email address available to add-on developers, so they can't contact you to resolve your issue. See this add-on's <a href=\"%(support)s"
"\">support section</a> to find out if assistance is available. You can also try asking the <a href=\"https://discourse.mozilla-community.org/c/add-ons/add-on-support\">add-on community</a> for "
"help. </li> <li>Please keep reviews clean, avoid the use of improper language and do not post any personal information. </li> </ul> <p>Please read the <a href=\"%(guide)s\">Review Guidelines</a> "
"for more detail about user add-on reviews.</p>"
"Please do not post bug reports here. We do not make your email address available to add-on developers, so they can't contact you to resolve your issue. See this add-on's <a "
"href=\"%(support)s\">support section</a> to find out if assistance is available. You can also try asking the <a href=\"https://discourse.mozilla-community.org/c/add-ons/add-on-support\">add-on "
"community</a> for help. </li> <li>Please keep reviews clean, avoid the use of improper language and do not post any personal information. </li> </ul> <p>Please read the <a href=\"%(guide)s\">Review "
"Guidelines</a> for more detail about user add-on reviews.</p>"
msgstr ""
#: src/olympia/reviews/templates/reviews/reply.html
@ -10391,7 +10371,7 @@ msgstr ""
#: src/olympia/search/templates/search/personas.html
msgid "Themes Search Results"
msgstr ""
msgstr "Tema Arama Sonuçları"
#. {0} is a tag, such as jetpack.
#: src/olympia/search/templates/search/results.html
@ -10412,7 +10392,7 @@ msgstr[0] "<b>{0}</b> kullanıcı"
#: src/olympia/search/templates/search/results.html
msgid "Filter Results"
msgstr ""
msgstr "Sonuçları filtrele"
#: src/olympia/search/templates/search/results.html
#, fuzzy
@ -10427,7 +10407,7 @@ msgstr "Etiketler"
#: src/olympia/search/templates/search/mobile/results.html
msgid "Search Results <i>({num})</i>"
msgstr ""
msgstr "Arama Sonuçları <i>({num})</i>"
#: src/olympia/signing/views.py src/olympia/templates/base.html src/olympia/templates/impala/base.html
#, fuzzy
@ -10611,8 +10591,8 @@ msgstr ""
msgid "Statistics Dashboard :: Add-ons for {0}"
msgstr ""
# %1 is an add-on name.
#. {0} is an add-on name
# %1 is an add-on name.
#: src/olympia/stats/templates/stats/stats.html
#, fuzzy
msgid "Statistics for {0}"
@ -10823,11 +10803,11 @@ msgstr ""
#: src/olympia/stats/templates/stats/reports/sources.html
#, python-format
msgid ""
"<h2>Tracking external sources</h2> <p> If you link to your add-on's details page or directly to its file from an external site, such as your blog or website, you can append a parameter to be "
"tracked as an additional download source on this page. For example, the following links would appear as sourced by your blog: <dl> <dt>Add-on Details Page</dt> <dd>https://addons.mozilla.org/addon/"
"%(slug)s?src=<b>external-blog</b></dd> <dt>Direct File Link</dt> <dd>https://addons.mozilla.org/downloads/latest/%(id)s/addon-%(id)s-latest.xpi?src=<b>external-blog</b></dd> </dl> <p> Only src "
"parameters that begin with \"external-\" will be tracked, up to 61 additional characters. Any text after \"external-\" can be used to describe the source, such as \"external-blog\", \"external-"
"sidebar\", \"external-campaign225\", etc. The following URL-safe characters are allowed: <code>a-z A-Z - . _ ~ %% +</code>"
"<h2>Tracking external sources</h2> <p> If you link to your add-on's details page or directly to its file from an external site, such as your blog or website, you can append a parameter to be tracked"
" as an additional download source on this page. For example, the following links would appear as sourced by your blog: <dl> <dt>Add-on Details Page</dt> "
"<dd>https://addons.mozilla.org/addon/%(slug)s?src=<b>external-blog</b></dd> <dt>Direct File Link</dt> <dd>https://addons.mozilla.org/downloads/latest/%(id)s/addon-%(id)s-latest.xpi?src=<b>external-"
"blog</b></dd> </dl> <p> Only src parameters that begin with \"external-\" will be tracked, up to 61 additional characters. Any text after \"external-\" can be used to describe the source, such as "
"\"external-blog\", \"external-sidebar\", \"external-campaign225\", etc. The following URL-safe characters are allowed: <code>a-z A-Z - . _ ~ %% +</code>"
msgstr ""
#: src/olympia/stats/templates/stats/reports/statuses.html
@ -11743,7 +11723,7 @@ msgstr "E-postaları göndermek yerine kaydet"
#: src/olympia/zadmin/forms.py
msgid "Deleted versions can`t be changed."
msgstr ""
msgstr "Silinmiş sürümler değiştirilemez."
# Plural in this context means many of the add-on type
# 75%

Просмотреть файл

@ -1,11 +1,11 @@
#
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-02-09 11:33+0000\n"
"PO-Revision-Date: 2016-09-02 18:29+0000\n"
"Last-Translator: burak <burak@buraksakalli.org>\n"
"PO-Revision-Date: 2017-02-15 16:57+0000\n"
"Last-Translator: Selim Şumlu <selim@sum.lu>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
@ -114,11 +114,11 @@ msgstr "Gönderiminiz otomatik olarak imzalanacak."
#: static/js/common/upload-addon.js
msgid "WebExtension upgrade"
msgstr ""
msgstr "WebExtension yükseltmesi"
#: static/js/common/upload-addon.js
msgid "We allow and encourage an upgrade but you cannot reverse this process. Once your users have the WebExtension installed, they will not be able to install a legacy add-on."
msgstr ""
msgstr "Yükseltmeye izin veriyoruz ve hatta öneriyoruz ama bu işlemi geri alamazsınız. Kullanıcılarınız WebExtension'ı yükledikten sonra eski nesil eklentiyi yükleyemezler."
#: static/js/common/upload-addon.js
msgid "Porting a legacy Firefox add-on on MDN"
@ -145,8 +145,8 @@ msgid ""
"Compiled binaries, as well as minified or obfuscated scripts (excluding known libraries) need to have their sources submitted separately for review. Make sure that you use the source code upload "
"field to avoid having your submission rejected."
msgstr ""
"Derlenmiş ikililer, ayrıca küçültülmüş veya karartılmış betikler (bilinen kitaplıklar hariç) incelenecekse, bunların kendi kaynaklarının ayrı olarak bulunması gerekir. Başvurunuzun geri "
"çevrilmesini önlemek adına, kaynak kodu gönderme sahasını kullandığınızdan emin olun."
"Derlenmiş ikililer, ayrıca küçültülmüş veya karartılmış betikler (bilinen kitaplıklar hariç) incelenecekse, bunların kendi kaynaklarının ayrı olarak bulunması gerekir. Başvurunuzun geri çevrilmesini"
" önlemek adına, kaynak kodu gönderme sahasını kullandığınızdan emin olun."
#: static/js/common/upload-addon.js
msgid "The validation process found these issues that can lead to rejections:"
@ -254,9 +254,8 @@ msgid "Search add-ons for <b>{0}</b>"
msgstr "<b>{0}</b> için eklentileri ara"
#: static/js/impala/users.js
#, fuzzy
msgid "Use original"
msgstr "orijinalini kullan"
msgstr "Orijinalini kullan"
#: static/js/impala/stats/chart.js
msgid "Week of {0}"
@ -1029,11 +1028,11 @@ msgstr "Hiçbir sürüm notu bulunamadı"
#: static/js/zamboni/editors.js
msgid "Review Text"
msgstr ""
msgstr "İnceleme Metni"
#: static/js/zamboni/editors.js
msgid "Review notes found"
msgstr ""
msgstr "İnceleme notları bulundu"
#: static/js/zamboni/editors.js
msgid "Average Reviews"
@ -1041,7 +1040,7 @@ msgstr ""
#: static/js/zamboni/editors.js
msgid "Number of Reviews"
msgstr ""
msgstr "İncelemelerin Sayısı"
#: static/js/zamboni/editors.js
msgid "Error loading translation"
@ -1112,7 +1111,7 @@ msgstr "Tema"
#: static/js/zamboni/themes_review_templates.js
msgid "Reviewer"
msgstr ""
msgstr "İnceleyen"
#: static/js/zamboni/themes_review_templates.js
msgid "Status"

Просмотреть файл

@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: addons-trunk // addons.mozilla.org\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-02-09 11:33+0000\n"
"PO-Revision-Date: 2017-02-09 17:08+0000\n"
"PO-Revision-Date: 2017-02-19 17:09+0000\n"
"Last-Translator: Pin-guang Chen <petercpg@mail.moztw.org>\n"
"Language-Team: Traditional Chinese (Taiwan) <LL@li.org>\n"
"Language: zh_TW\n"
@ -5341,7 +5341,7 @@ msgstr "移除這個應用程式"
#: src/olympia/devhub/templates/devhub/includes/macros.html
msgid "Select <b>up to {0}</b> {1} category for this add-on:"
msgid_plural "Select <b>up to {0}</b> {1} categories for this add-on:"
msgstr[0] "為這個附加元件選擇<b>最多 {0}</b> {1} 個分類:"
msgstr[0] "為此附加元件選擇<b>最多 {0} 個</b> {1} 分類:"
#: src/olympia/devhub/templates/devhub/includes/policy_form.html
msgid ""

Просмотреть файл

@ -9,7 +9,7 @@
"dependencies": {
"addons-linter": "0.15.15",
"clean-css": "4.0.7",
"clean-css-cli": "4.0.6",
"clean-css-cli": "4.0.7",
"less": "2.7.2",
"stylus": "0.54.5",
"uglify-js": "2.7.5"

Просмотреть файл

@ -172,9 +172,9 @@ django-waffle==0.11.1 \
--hash=sha256:d135816e416c5846aff1928634513f89cc83af4e939e2007e4a9707ef20d7c02 \
--hash=sha256:68d246e6f5562309f05c2743ae536b2569933e7d8ed65ecf3606e7a780968b68
# djangorestframework is required by drf-nested-routers
djangorestframework==3.5.3 \
--hash=sha256:f446041a944723e14502a0a5880d0bc74a499ac1075781177f2fa6d7fe7b415d \
--hash=sha256:7c04e2a45e6d30df7042749b9a029882c449eb2fee038f3cc14dfbc93581bfbf
djangorestframework==3.5.4 \
--hash=sha256:110afa12784ceadfb50808882689302d266785b51e3d13286744333ff6d78e60 \
--hash=sha256:f995a35ae22f354d2a9a42ee6d2c059c101f826b1485ed46781677895ad25ee5
djangorestframework-jwt==1.9.0 \
--hash=sha256:c9fa3c1847569ac1c96d04c7fa38f8dacf894ab405d9e772f0776c4d57fa35ad \
--hash=sha256:319fd2de82a798f46c03c0289e790f2ddbe65dbf7196ed46815225497606abb1
@ -339,9 +339,9 @@ pytz==2016.10 \
--hash=sha256:7016b2c4fa075c564b81c37a252a5fccf60d8964aa31b7f5eae59aeb594ae02b \
--hash=sha256:9a43e20aa537cfad8fe7a1715165c91cb4a6935d40947f2d070e4c80f2dcd22b \
--hash=sha256:aafbf066975fe217ed49d7d197b26903d3b43e9ca2aa6ba0a211081f13c41917
raven==5.32.0 \
--hash=sha256:a06517e9b7858ac41963f9741cc8358005d37511475aaa4c8b692d29ae0a7d94 \
--hash=sha256:13e68bbf21d4d6f0cae4458da5be2d0d538733beabc5f93cb185048b28a77457
raven==6.0.0 \
--hash=sha256:c9b521eaa4f253b5ed96051ab8f7c8e61fe901f9fc18ee6d9ac99726fbcb14fe \
--hash=sha256:cee2d745c762230383fc89365770552c93c71205ee92c6e6bbbd85fe4dbb2803
# rdflib is required by amo-validator
rdflib==3.4.0 \
--hash=sha256:78d5f11a7001661d7637f9e61554a5f8971e197f3f6d17ba5e4039b0668116cf # pyup: ==3.4.0

Просмотреть файл

@ -109,10 +109,13 @@ class Update(object):
sql = """SELECT id, status, addontype_id, guid FROM addons
WHERE guid = %(guid)s AND
inactive = 0 AND
status != %(STATUS_DELETED)s
status NOT IN (%(STATUS_DELETED)s, %(STATUS_DISABLED)s)
LIMIT 1;"""
self.cursor.execute(sql, {'guid': self.data['id'],
'STATUS_DELETED': base.STATUS_DELETED})
self.cursor.execute(sql, {
'guid': self.data['id'],
'STATUS_DELETED': base.STATUS_DELETED,
'STATUS_DISABLED': base.STATUS_DISABLED,
})
result = self.cursor.fetchone()
if result is None:
return False
@ -135,7 +138,6 @@ class Update(object):
data['STATUS_PUBLIC'] = base.STATUS_PUBLIC
data['STATUS_BETA'] = base.STATUS_BETA
data['STATUS_DISABLED'] = base.STATUS_DISABLED
data['RELEASE_CHANNEL_LISTED'] = base.RELEASE_CHANNEL_LISTED
sql = ["""
@ -355,7 +357,6 @@ def application(environ, start_response):
output = force_bytes(update.get_rdf())
start_response(status, update.get_headers(len(output)))
except:
#mail_exception(data)
log_exception(data)
raise
return [output]

Просмотреть файл

@ -28,7 +28,6 @@ from olympia.lib.log_settings_base import formatters, handlers
# remove all this.
from olympia.constants.applications import APPS_ALL
from olympia.constants.platforms import PLATFORMS
from olympia.constants.base import STATUS_DISABLED
# This is not DRY: it's a copy of amo.helpers.user_media_path, to avoid an

Просмотреть файл

@ -113,9 +113,18 @@ FXA_CONFIG = {
'redirect_url': 'http://localhost:3000/fxa-authenticate',
'scope': 'profile',
},
'local': {
'client_id': '1778aef72d1adfb3',
'client_secret':
'3feebe3c009c1a0acdedd009f3530eae2b88859f430fa8bb951ea41f2f859b18',
'content_host': 'https://stable.dev.lcip.org',
'oauth_host': 'https://oauth-stable.dev.lcip.org/v1',
'profile_host': 'https://stable.dev.lcip.org/profile/v1',
'redirect_url': 'http://localhost:3000/api/v3/accounts/authenticate/',
'scope': 'profile',
},
}
FXA_CONFIG['amo'] = FXA_CONFIG['internal']
FXA_CONFIG['local'] = FXA_CONFIG['internal']
ALLOWED_FXA_CONFIGS = ['default', 'amo', 'local']
# CSP report endpoint which returns a 204 from addons-nginx in local dev.

Просмотреть файл

@ -507,6 +507,24 @@ class TestWithUser(TestCase):
self.request, views.ERROR_STATE_MISMATCH, next_path='/next',
format='json')
def test_dynamic_configuration(self):
fxa_config = {'some': 'config'}
class LoginView(object):
def get_fxa_config(self, request):
return fxa_config
@views.with_user(format='json')
def post(*args, **kwargs):
return args, kwargs
identity = {'uid': '1234', 'email': 'hey@yo.it'}
self.fxa_identify.return_value = identity
self.find_user.return_value = self.user
self.request.data = {'code': 'foo', 'state': 'some-blob'}
LoginView().post(self.request)
self.fxa_identify.assert_called_with('foo', config=fxa_config)
class TestRegisterUser(TestCase):
@ -995,16 +1013,6 @@ class TestParseNextPath(TestCase):
u'/en-US/firefox/addon/dęlîcíøùs-päñčåkę/?src=hp-dl-featured')
class TestAccountViewSetGet(TestCase):
def test_basic(self):
self.user = user_factory()
self.url = reverse(
'account-detail', kwargs={'pk': self.user.pk})
with self.assertRaises(NotImplementedError):
self.client.get(self.url)
class TestSessionView(TestCase):
def login_user(self, user):
identity = {

Просмотреть файл

@ -1,23 +1,9 @@
from django.conf.urls import include, url
from django.conf.urls import url
from rest_framework.routers import SimpleRouter
from rest_framework_nested.routers import NestedSimpleRouter
from olympia.reviews.views import ReviewViewSet
from . import views
accounts = SimpleRouter()
accounts.register(r'account', views.AccountViewSet, base_name='account')
# Router for children of /accounts/account/{account_pk}/.
sub_accounts = NestedSimpleRouter(accounts, r'account', lookup='account')
sub_accounts.register('reviews', ReviewViewSet, base_name='account-review')
urlpatterns = [
url(r'', include(accounts.urls)),
url(r'', include(sub_accounts.urls)),
url(r'^authenticate/$', views.AuthenticateView.as_view(),
name='accounts.authenticate'),
url(r'^login/$', views.LoginView.as_view(), name='accounts.login'),

Просмотреть файл

@ -15,11 +15,9 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import generics
from rest_framework.authentication import SessionAuthentication
from rest_framework.mixins import RetrieveModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet
from rest_framework_jwt.settings import api_settings as jwt_api_settings
from waffle.decorators import waffle_switch
@ -162,8 +160,11 @@ def with_user(format, config=None):
@write
def inner(self, request):
if config is None:
fxa_config = (
settings.FXA_CONFIG[settings.DEFAULT_FXA_CONFIG_NAME])
if hasattr(self, 'get_fxa_config'):
fxa_config = self.get_fxa_config(request)
else:
fxa_config = (
settings.FXA_CONFIG[settings.DEFAULT_FXA_CONFIG_NAME])
else:
fxa_config = config
@ -267,20 +268,16 @@ class LoginStartView(LoginStartBaseView):
class LoginBaseView(FxAConfigMixin, APIView):
def post(self, request):
config = self.get_fxa_config(request)
@with_user(format='json', config=config)
def _post(self, request, user, identity, next_path):
if user is None:
return Response({'error': ERROR_NO_USER}, status=422)
else:
update_user(user, identity)
response = Response({'email': identity['email']})
add_api_token_to_response(response, user, set_cookie=False)
log.info('Logging in user {} from FxA'.format(user))
return response
return _post(self, request)
@with_user(format='json')
def post(self, request, user, identity, next_path):
if user is None:
return Response({'error': ERROR_NO_USER}, status=422)
else:
update_user(user, identity)
response = Response({'email': identity['email']})
add_api_token_to_response(response, user, set_cookie=False)
log.info('Logging in user {} from FxA'.format(user))
return response
def options(self, request):
return Response()
@ -306,7 +303,10 @@ class RegisterView(APIView):
return response
class AuthenticateView(APIView):
class AuthenticateView(FxAConfigMixin, APIView):
DEFAULT_FXA_CONFIG_NAME = settings.DEFAULT_FXA_CONFIG_NAME
ALLOWED_FXA_CONFIGS = settings.ALLOWED_FXA_CONFIGS
authentication_classes = (SessionAuthentication,)
@with_user(format='html')
@ -344,16 +344,6 @@ class ProfileView(generics.RetrieveAPIView):
return Response(self.get_serializer(request.user).data)
class AccountViewSet(RetrieveModelMixin, GenericViewSet):
queryset = UserProfile.objects.all()
def retrieve(self, request, *args, **kwargs):
# See https://github.com/mozilla/addons-server/issues/3138 ; be careful
# when implementing it, we don't want to list users, only retrieve them
# and eventually modify them through this ViewSet.
raise NotImplementedError
class AccountSuperCreate(APIView):
authentication_classes = [JWTKeyAuthentication]
permission_classes = [

Просмотреть файл

@ -24,11 +24,7 @@ class ActivityLogSerializer(serializers.ModelSerializer):
self.to_highlight = kwargs.get('context', []).get('to_highlight', [])
def get_comments(self, obj):
# We have to do .unescape on the output of obj.to_string because the
# 'string' it returns is supposed to be used in markup and doesn't get
# serialized correctly unless we unescape it.
comments = (obj.details['comments'] if obj.details
else obj.to_string().unescape())
comments = obj.details['comments'] if obj.details else ''
return getattr(obj.log(), 'sanitize', comments)
def get_action_label(self, obj):

Просмотреть файл

@ -79,5 +79,5 @@ class TestReviewNotesSerializerOutput(TestCase, LogMixin):
self.addon.find_latest_version(channel=amo.RELEASE_CHANNEL_LISTED),
user=self.user)
result = self.serialize()
# Should default to the string representation of the log event.
assert result['comments'] == self.entry.to_string()
# Should output an empty string.
assert result['comments'] == ''

Просмотреть файл

@ -274,6 +274,30 @@ class TestLogAndNotify(TestCase):
self._check_email(send_mail_mock.call_args_list[1],
self.addon.get_dev_url('versions'))
@mock.patch('olympia.activity.utils.send_mail')
def test_log_with_no_comment(self, send_mail_mock):
# One from the reviewer.
self._create(amo.LOG.REJECT_VERSION, self.reviewer)
action = amo.LOG.APPROVAL_NOTES_CHANGED
log_and_notify(
action=action, comments=None, note_creator=self.developer,
version=self.version)
logs = ActivityLog.objects.filter(action=action.id)
assert len(logs) == 1
assert not logs[0].details # No details json because no comment.
assert send_mail_mock.call_count == 2 # One author, one reviewer.
recipients = self._recipients(send_mail_mock)
assert len(recipients) == 2
assert self.reviewer.email in recipients
assert self.developer2.email in recipients
assert u'Approval notes changed' in (
send_mail_mock.call_args_list[0][0][1])
assert u'Approval notes changed' in (
send_mail_mock.call_args_list[1][0][1])
def test_staff_cc_group_is_empty_no_failure(self):
Group.objects.create(name=ACTIVITY_MAIL_GROUP, rules='None:None')
log_and_notify(amo.LOG.REJECT_VERSION, u'á', self.reviewer,

Просмотреть файл

@ -13,7 +13,7 @@ from olympia.access import acl
from olympia.activity.models import ActivityLogToken
from olympia.amo.helpers import absolutify
from olympia.amo.urlresolvers import reverse
from olympia.amo.utils import send_mail
from olympia.amo.utils import no_translation, send_mail
from olympia.devhub.models import ActivityLog
from olympia.users.models import UserProfile
from olympia.users.utils import get_task_user
@ -156,9 +156,17 @@ def log_and_notify(action, comments, note_creator, version):
log_kwargs = {
'user': note_creator,
'created': datetime.datetime.now(),
'details': {
}
if comments:
log_kwargs['details'] = {
'comments': comments,
'version': version.version}}
'version': version.version}
else:
# Just use the name of the action if no comments provided. Alas we
# can't know the locale of recipient, and our templates are English
# only so prevent language jumble by forcing into en-US.
with no_translation():
comments = '%s' % action.short
note = amo.log(action, version.addon, version, **log_kwargs)
# Collect reviewers involved with this version.

Просмотреть файл

@ -1,10 +1,9 @@
from django.conf.urls import include, patterns, url
from django.conf.urls import include, url
from rest_framework.routers import SimpleRouter
from rest_framework_nested.routers import NestedSimpleRouter
from olympia.activity.views import VersionReviewNotesViewSet
from olympia.reviews.views import ReviewViewSet
from .views import (
AddonFeaturedView, AddonSearchView, AddonVersionViewSet, AddonViewSet,
@ -17,17 +16,15 @@ addons.register(r'addon', AddonViewSet)
# Router for children of /addons/addon/{addon_pk}/.
sub_addons = NestedSimpleRouter(addons, r'addon', lookup='addon')
sub_addons.register('versions', AddonVersionViewSet, base_name='addon-version')
sub_addons.register('reviews', ReviewViewSet, base_name='addon-review')
sub_versions = NestedSimpleRouter(sub_addons, r'versions', lookup='version')
sub_versions.register(r'reviewnotes', VersionReviewNotesViewSet,
base_name='version-reviewnotes')
urlpatterns = patterns(
'',
urlpatterns = [
url(r'', include(addons.urls)),
url(r'', include(sub_addons.urls)),
url(r'', include(sub_versions.urls)),
url(r'^search/$', AddonSearchView.as_view(), name='addon-search'),
url(r'^featured/$', AddonFeaturedView.as_view(), name='addon-featured'),
url(r'^categories/$', StaticCategoryView.as_view(), name='category-list'),
)
]

Просмотреть файл

@ -49,6 +49,8 @@ class AddonIndexer(BaseSearchIndexer):
'type': 'byte', 'index': 'no'},
'size': {'type': 'long', 'index': 'no'},
'status': {'type': 'byte'},
'webext_permissions_list': {
'type': 'string', 'index': 'no'},
}
},
'version': {'type': 'string', 'index': 'no'},
@ -158,6 +160,7 @@ class AddonIndexer(BaseSearchIndexer):
'platform': file_.platform,
'size': file_.size,
'status': file_.status,
'webext_permissions_list': file_.webext_permissions_list,
} for file_ in version_obj.all_files],
'reviewed': version_obj.reviewed,
'version': version_obj.version,

Просмотреть файл

@ -1344,14 +1344,6 @@ class Addon(OnChangeMixin, ModelBase):
return self.installed.filter(user=user).exists()
def get_latest_file(self):
"""Get the latest file from the current version."""
cur = self.current_version
if cur:
res = cur.files.order_by('-created')
if res:
return res[0]
def in_escalation_queue(self):
return self.escalationqueue_set.exists()

Просмотреть файл

@ -4,7 +4,7 @@ from olympia import amo
from olympia.addons.models import (
Addon, AddonFeatureCompatibility, attach_tags, Persona, Preview)
from olympia.amo.helpers import absolutify
from olympia.amo.urlresolvers import reverse
from olympia.amo.urlresolvers import get_outgoing_url, reverse
from olympia.api.fields import ReverseChoiceField, TranslationSerializerField
from olympia.api.serializers import BaseESSerializer
from olympia.applications.models import AppVersion
@ -30,10 +30,14 @@ class FileSerializer(serializers.ModelSerializer):
url = serializers.SerializerMethodField()
platform = ReverseChoiceField(choices=amo.PLATFORM_CHOICES_API.items())
status = ReverseChoiceField(choices=amo.STATUS_CHOICES_API.items())
permissions = serializers.ListField(
source='webext_permissions_list',
child=serializers.CharField())
class Meta:
model = File
fields = ('id', 'created', 'hash', 'platform', 'size', 'status', 'url')
fields = ('id', 'created', 'hash', 'platform', 'size', 'status', 'url',
'permissions')
def get_url(self, obj):
# File.get_url_path() is a little different, it's already absolute, but
@ -206,8 +210,21 @@ class AddonSerializer(serializers.ModelSerializer):
def to_representation(self, obj):
data = super(AddonSerializer, self).to_representation(obj)
if data['theme_data'] is None:
if 'theme_data' in data and data['theme_data'] is None:
data.pop('theme_data')
if 'homepage' in data:
data['homepage'] = self.outgoingify(data['homepage'])
if 'support_url' in data:
data['support_url'] = self.outgoingify(data['support_url'])
return data
def outgoingify(self, data):
if isinstance(data, basestring):
return get_outgoing_url(data)
elif isinstance(data, dict):
return {key: get_outgoing_url(value) if value else None
for key, value in data.items()}
# Probably None... don't bother.
return data
def get_categories(self, obj):
@ -295,6 +312,15 @@ class ESBaseAddonSerializer(BaseESSerializer):
translated_fields = ('name', 'description', 'homepage', 'summary',
'support_email', 'support_url')
def fake_file_object(self, obj, data):
file_ = File(
id=data['id'], created=self.handle_date(data['created']),
hash=data['hash'], filename=data['filename'],
platform=data['platform'], size=data['size'],
status=data['status'], version=obj)
file_.webext_permissions_list = data.get('webext_permissions_list', [])
return file_
def fake_version_object(self, obj, data, channel):
if data:
version = Version(
@ -302,12 +328,8 @@ class ESBaseAddonSerializer(BaseESSerializer):
reviewed=self.handle_date(data['reviewed']),
version=data['version'], channel=channel)
version.all_files = [
File(
id=file_['id'], created=self.handle_date(file_['created']),
hash=file_['hash'], filename=file_['filename'],
platform=file_['platform'], size=file_['size'],
status=file_['status'], version=version)
for file_ in data.get('files', [])
self.fake_file_object(version, file_data)
for file_data in data.get('files', [])
]
# In ES we store integers for the appversion info, we need to

Просмотреть файл

@ -37,7 +37,9 @@
</p>
</div> {# install #}
{% if b.detailed %}
<div class="detailed">
{% if addon.privacy_policy %}
<a class="privacy-policy badge" href="{{ url('addons.privacy', addon.slug) }}">
{{ _('Privacy Policy') }}
@ -48,7 +50,16 @@
{{ _('End-User License Agreement') }}
</a>
{% endif %}
{% if waffle.switch('webext-permissions') and version and version.all_files[0] %}
<br>
<a class="webext-permissions badge" href="#">
{% if not version.all_files[0].is_webextension %}
<img src="{{ static('img/developers/test-warning.png') }}" alt="{{ _('[Warning]') }}">
{% endif %}
{{ _('Permissions') }}
</a>
{% endif %}
</div>
{% if addon.is_unreviewed() %}
<p class="warning">
{% trans url=url('pages.faq') + "#unreviewed" %}

Просмотреть файл

@ -322,6 +322,44 @@
</div>
</div>
{% endif %}
{% if waffle.switch('webext-permissions') and addon.current_version and addon.current_version.all_files[0] %}
<div class="modal" id="webext-permissions">
<a href="#" class="close">{{ _('close') }}</a>
<h2>{{ _('Permissions') }}</h2>
{% if addon.current_version.all_files[0].is_webextension %}
{% set permissions = addon.current_version.all_files[0].webext_permissions %}
<div class="prose">
<p>
{{ _('Some add-ons ask for permissions to perform certain functions (example: a tab management add-on will ask permission to access your browsers tab system).') }}
</p><p>
{{ _('Since youre in control of your Firefox, the choice to grant or deny these requests is yours. Accepting permissions does not inherently compromise your browsers performance or security, but in some rare cases risk may be involved.') }}
</p>
</div>
<div>
<p>
{# l10n: This is a header of a list of things the add-on can do. #}
<h3>{{ _('It can:') }}</h3>
<ul class="webext-permissions-list">
{% for perm in permissions %}
<li class="webext-permissions-list">{{ perm.description|e }}</li>
{% else %}
{{ ("The add-on doesn't have any special permissions.") }}
{% endfor %}
</ul>
</p>
</div>
{% else %}
<div class="prose">
<img src="{{ static('img/developers/test-warning.png') }}" alt="{{ _('[Warning]') }}">
<p>
{{ ('Some add-ons ask for permissions to perform certain functions. Since youre in control of your Firefox, the choice to grant or deny these requests is yours.') }}
</p><p>
{{ ('Please note this add-on uses legacy technology, which gives it access to all browser functions and data without requesting your permission.') }}
</p>
</div>
{% endif %}
</div>
{% endif %}
{% if addon.eula %}
<div class="modal" id="eula">
<a href="#" class="close">{{ _('close') }}</a>

Просмотреть файл

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from datetime import timedelta
from itertools import chain
from olympia import amo
@ -12,6 +11,7 @@ from olympia.addons.indexers import AddonIndexer
from olympia.constants.applications import FIREFOX
from olympia.constants.platforms import PLATFORM_ALL, PLATFORM_MAC
from olympia.constants.search import SEARCH_ANALYZER_MAP
from olympia.files.models import WebextPermission
class TestAddonIndexer(TestCase):
@ -116,7 +116,7 @@ class TestAddonIndexer(TestCase):
# Make sure files mapping is set inside current_version.
files_mapping = version_mapping['files']['properties']
expected_file_keys = ('id', 'created', 'filename', 'hash', 'platform',
'size', 'status')
'size', 'status', 'webext_permissions_list')
assert set(files_mapping.keys()) == set(expected_file_keys)
def _extract(self):
@ -178,13 +178,15 @@ class TestAddonIndexer(TestCase):
version = self.addon.current_version
file_factory(version=version, platform=PLATFORM_MAC.id)
current_beta_version = version_factory(
addon=self.addon, file_kw={'status': amo.STATUS_BETA})
addon=self.addon,
file_kw={'status': amo.STATUS_BETA, 'is_webextension': True})
# Give one of the versions some webext permissions to test that.
WebextPermission.objects.create(
file=current_beta_version.all_files[0],
permissions=['bookmarks', 'random permission']
)
unlisted_version = version_factory(
addon=self.addon, channel=amo.RELEASE_CHANNEL_UNLISTED)
# FIXME: remove this next line once current_version is modified to only
# return listed versions.
unlisted_version.update(
created=version.created - timedelta(days=42))
extracted = self._extract()
assert extracted['current_version']
@ -208,6 +210,7 @@ class TestAddonIndexer(TestCase):
assert extracted_file['platform'] == file_.platform
assert extracted_file['size'] == file_.size
assert extracted_file['status'] == file_.status
assert extracted_file['webext_permissions_list'] == []
assert set(extracted['platforms']) == set([PLATFORM_MAC.id,
PLATFORM_ALL.id])
@ -232,6 +235,9 @@ class TestAddonIndexer(TestCase):
assert extracted_file['platform'] == file_.platform
assert extracted_file['size'] == file_.size
assert extracted_file['status'] == file_.status
assert (extracted_file['webext_permissions_list'] ==
file_.webext_permissions_list ==
['bookmarks', 'random permission'])
version = unlisted_version
assert extracted['latest_unlisted_version']
@ -255,6 +261,7 @@ class TestAddonIndexer(TestCase):
assert extracted_file['platform'] == file_.platform
assert extracted_file['size'] == file_.size
assert extracted_file['status'] == file_.status
assert extracted_file['webext_permissions_list'] == []
def test_extract_translations(self):
translations_name = {

Просмотреть файл

@ -2401,7 +2401,7 @@ class TestAddonFromUpload(UploadTest):
parse_addon.return_value = {
'default_locale': u'en',
'e10s_compatibility': 2,
'guid': u'notify-link-clicks-i18n@mozilla.org',
'guid': u'notify-link-clicks-i18n@notzilla.org',
'name': u'__MSG_extensionName__',
'is_webextension': True,
'type': 1,
@ -2426,7 +2426,7 @@ class TestAddonFromUpload(UploadTest):
parse_addon.return_value = {
'default_locale': u'xxx',
'e10s_compatibility': 2,
'guid': u'notify-link-clicks-i18n@mozilla.org',
'guid': u'notify-link-clicks-i18n@notzilla.org',
'name': u'__MSG_extensionName__',
'is_webextension': True,
'type': 1,

Просмотреть файл

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from django.utils.translation import override
from elasticsearch_dsl import Search
from rest_framework.test import APIRequestFactory
@ -7,7 +9,7 @@ from olympia.amo.helpers import absolutify
from olympia.amo.tests import (
addon_factory, ESTestCase, file_factory, TestCase, version_factory,
user_factory)
from olympia.amo.urlresolvers import reverse
from olympia.amo.urlresolvers import get_outgoing_url, reverse
from olympia.addons.indexers import AddonIndexer
from olympia.addons.models import (
Addon, AddonCategory, AddonUser, Category, Persona, Preview)
@ -17,6 +19,7 @@ from olympia.addons.serializers import (
VersionSerializer)
from olympia.addons.utils import generate_addon_guid
from olympia.constants.categories import CATEGORIES
from olympia.files.models import WebextPermission
from olympia.versions.models import ApplicationsVersions, AppVersion, License
@ -51,6 +54,7 @@ class AddonSerializerOutputTestMixin(object):
assert result_file['size'] == file_.size
assert result_file['status'] == amo.STATUS_CHOICES_API[file_.status]
assert result_file['url'] == file_.get_url_path(src='')
assert result_file['permissions'] == file_.webext_permissions_list
assert data['edit_url'] == absolutify(
self.addon.get_dev_url(
@ -159,7 +163,9 @@ class AddonSerializerOutputTestMixin(object):
assert result['guid'] == self.addon.guid
assert result['has_eula'] is False
assert result['has_privacy_policy'] is False
assert result['homepage'] == {'en-US': self.addon.homepage}
assert result['homepage'] == {
'en-US': get_outgoing_url(unicode(self.addon.homepage))
}
assert result['icon_url'] == absolutify(self.addon.get_icon_url(64))
assert result['is_disabled'] == self.addon.is_disabled
assert result['is_experimental'] == self.addon.is_experimental is False
@ -200,7 +206,9 @@ class AddonSerializerOutputTestMixin(object):
assert result['status'] == 'public'
assert result['summary'] == {'en-US': self.addon.summary}
assert result['support_email'] == {'en-US': self.addon.support_email}
assert result['support_url'] == {'en-US': self.addon.support_url}
assert result['support_url'] == {
'en-US': get_outgoing_url(unicode(self.addon.support_url))
}
assert 'theme_data' not in result
assert set(result['tags']) == set(['some_tag', 'some_other_tag'])
assert result['type'] == 'extension'
@ -334,12 +342,32 @@ class AddonSerializerOutputTestMixin(object):
'en-US': u'My Addôn description in english',
'fr': u'Description de mon Addôn',
}
translated_homepages = {
'en-US': u'http://www.google.com/',
'fr': u'http://www.googlé.fr/',
}
self.addon = addon_factory()
self.addon.description = translated_descriptions
self.addon.homepage = translated_homepages
self.addon.save()
result = self.serialize()
assert result['description'] == translated_descriptions
assert result['homepage'] != translated_homepages
assert result['homepage'] == {
'en-US': get_outgoing_url(translated_homepages['en-US']),
'fr': get_outgoing_url(translated_homepages['fr'])
}
# Try a single translation. The locale activation is normally done by
# LocaleAndAppURLMiddleware, but since we're directly calling the
# serializer we need to do it ourselves.
self.request = APIRequestFactory().get('/', {'lang': 'fr'})
with override('fr'):
result = self.serialize()
assert result['description'] == translated_descriptions['fr']
assert result['homepage'] == get_outgoing_url(
translated_homepages['fr'])
def test_persona_with_persona_id(self):
self.addon = addon_factory(persona_id=42, type=amo.ADDON_PERSONA)
@ -387,6 +415,23 @@ class AddonSerializerOutputTestMixin(object):
assert result['icon_url'] == (
'http://testserver/static/img/addon-icons/default-64.png')
def test_webextension(self):
self.addon = addon_factory(
file_kw={'is_webextension': True})
# Give one of the versions some webext permissions to test that.
WebextPermission.objects.create(
file=self.addon.current_version.all_files[0],
permissions=['bookmarks', 'random permission']
)
result = self.serialize()
self._test_version(
self.addon.current_version, result['current_version'])
# Double check the permissions got correctly set.
assert result['current_version']['files'][0]['permissions'] == ([
'bookmarks', 'random permission'])
class TestAddonSerializerOutput(AddonSerializerOutputTestMixin, TestCase):
serializer_class = AddonSerializer
@ -538,6 +583,20 @@ class TestVersionSerializerOutput(TestCase):
assert result['id'] == self.version.pk
assert result['license'] is None
def test_file_webext_permissions(self):
self.version = addon_factory().current_version
result = self.serialize()
# No permissions.
assert result['files'][0]['permissions'] == []
self.version = addon_factory(
file_kw={'is_webextension': True}).current_version
permissions = ['dangerdanger', 'high', 'voltage']
WebextPermission.objects.create(
permissions=permissions, file=self.version.all_files[0])
result = self.serialize()
assert result['files'][0]['permissions'] == permissions
class TestSimpleVersionSerializerOutput(TestCase):
def setUp(self):

Просмотреть файл

@ -84,6 +84,13 @@ class TestDataValidate(VersionCheckMixin, TestCase):
up = self.get(self.good_data)
assert not up.is_valid()
def test_disabled(self):
addon = Addon.objects.get(pk=3615)
addon.update(status=amo.STATUS_DISABLED)
up = self.get(self.good_data)
assert not up.is_valid()
def test_no_version(self):
data = self.good_data.copy()
del data['version']

Просмотреть файл

@ -26,6 +26,7 @@ from olympia.addons.models import (
AddonUser, Category, Charity, Persona)
from olympia.bandwagon.models import Collection
from olympia.constants.categories import CATEGORIES
from olympia.files.models import WebextPermission
from olympia.paypal.tests.test import other_error
from olympia.stats.models import Contribution
from olympia.users.helpers import users_list
@ -733,6 +734,71 @@ class TestDetailPage(TestCase):
privacy_url = reverse('addons.privacy', args=[self.addon.slug])
assert doc('.privacy-policy').attr('href').endswith(privacy_url)
@override_switch('webext-permissions', active=False)
def test_permissions_not_shown_without_waffle(self):
response = self.client.get(self.url)
doc = pq(response.content)
assert doc('a.webext-permissions').length == 0
assert doc('#webext-permissions').length == 0
@override_switch('webext-permissions', active=True)
def test_permissions_webext(self):
file_ = self.addon.current_version.all_files[0]
file_.update(is_webextension=True)
WebextPermission.objects.create(file=file_, permissions=[
u'http://*/*', u'<all_urls>', u'bookmarks',
u'made up permission', u'https://google.com/'])
response = self.client.get(self.url)
doc = pq(response.content)
# The link next to the button
assert doc('a.webext-permissions').length == 1
# And the model dialog
assert doc('#webext-permissions').length == 1
assert u'perform certain functions (example: a tab management' in (
doc('#webext-permissions div.prose').text())
assert doc('ul.webext-permissions-list').length == 1
assert doc('li.webext-permissions-list').length == 4
# See File.webext_permissions for the order logic
assert doc('li.webext-permissions-list').text() == (
u'Access your data for all websites '
u'Access bookmarks '
u'Access your data for https://google.com/ website '
u'made up permission')
@override_switch('webext-permissions', active=True)
def test_permissions_webext_no_permissions(self):
file_ = self.addon.current_version.all_files[0]
file_.update(is_webextension=True)
assert file_.webext_permissions_list == []
response = self.client.get(self.url)
doc = pq(response.content)
# The link next to the button - still shown even when no permissions.
assert doc('a.webext-permissions').length == 1
# And the model dialog
assert doc('#webext-permissions').length == 1
assert u'perform certain functions (example: a tab management' in (
doc('#webext-permissions div.prose').text())
assert doc('ul.webext-permissions-list').length == 1
assert doc('li.webext-permissions-list').length == 0
assert u"doesn't have any special" in (
doc('ul.webext-permissions-list').text())
@override_switch('webext-permissions', active=True)
def test_permissions_non_webext(self):
file_ = self.addon.current_version.all_files[0]
file_.update(is_webextension=False)
response = self.client.get(self.url)
doc = pq(response.content)
# The link next to the button
assert doc('a.webext-permissions').length == 1
# danger danger icon shown for oldie xul addons
assert doc('a.webext-permissions img').length == 1
# And the model dialog
assert doc('#webext-permissions').length == 1
assert u'Please note this add-on uses legacy technology' in (
doc('#webext-permissions div.prose').text())
assert doc('.webext-permissions-list').length == 0
def test_simple_html_is_rendered_in_privacy(self):
self.addon.privacy_policy = """
<strong> what the hell..</strong>

Просмотреть файл

@ -3,7 +3,6 @@ from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import redirect
from django.views.decorators.cache import cache_page
from olympia.reviews.urls import review_patterns
from olympia.stats.urls import stats_patterns
from . import buttons
from . import views
@ -12,8 +11,7 @@ ADDON_ID = r"""(?P<addon_id>[^/<>"']+)"""
# These will all start with /addon/<addon_id>/
detail_patterns = patterns(
'',
detail_patterns = [
url('^$', views.addon_detail, name='addons.detail'),
url('^more$', views.addon_detail, name='addons.detail_more'),
url('^eula/(?P<file_id>\d+)?$', views.eula, name='addons.eula'),
@ -39,10 +37,10 @@ detail_patterns = patterns(
addon_id, permanent=True),
name='addons.about'),
('^reviews/', include(review_patterns('addons'))),
('^statistics/', include(stats_patterns)),
('^versions/', include('olympia.versions.urls')),
)
url('^reviews/', include('olympia.reviews.urls')),
url('^statistics/', include(stats_patterns)),
url('^versions/', include('olympia.versions.urls')),
]
urlpatterns = patterns(

Просмотреть файл

@ -14,6 +14,7 @@ urlpatterns = patterns(
url(r'^v3/addons/', include('olympia.addons.api_urls')),
url(r'^v3/', include('olympia.discovery.api_urls')),
url(r'^v3/internal/', include('olympia.internal_tools.urls')),
url(r'^v3/reviews/', include('olympia.reviews.api_urls')),
url(r'^v3/', include('olympia.signing.urls')),
url(r'^v3/statistics/', include('olympia.stats.api_urls')),
url(r'^v3/activity/', include('olympia.activity.urls')),

Просмотреть файл

@ -1,9 +1,9 @@
<div class="barometer">
<form method="post" class="upvote" action="{{ up_action }}">
<form method="post" class="upvote" action="{{ up_action }}" data-no-csrf>
<input class="{{ up_class }}" value="{{ c.upvotes }}" type="submit"
title="{{ up_title }}">
</form>
<form method="post" class="downvote" action="{{ down_action }}">
<form method="post" class="downvote" action="{{ down_action }}" data-no-csrf>
<input class="{{ down_class }}" value="{{ c.downvotes }}" type="submit"
title="{{ down_title }}">
</form>

Просмотреть файл

@ -108,7 +108,7 @@
{{ num }} Add-ons in this Collection
{% endtrans %}
</h3>
<form class="item-sort go">
<form class="item-sort go" data-no-csrf>
<label for="sortby">{{ _('Sort by:') }}</label>
<select id="sortby" name="{{ filter.key }}">
{% for value, title in filter.opts %}

Просмотреть файл

@ -5,19 +5,68 @@ from django.utils.translation import ugettext_lazy as _lazy
Permission = namedtuple('Permission',
'name, description, long_description')
ALL_URLS_PERMISSION = Permission(
u'all_urls',
_lazy(u'Access your data for all websites'),
'')
WEBEXT_PERMISSIONS = {
u'activeTab': Permission(
u'activeTab',
_lazy(u'Requests that the extension be granted permissions according '
u'to the activeTab specification.'),
_lazy(u'Requests that the extension be granted permissions according '
u'to the activeTab specification.')),
u'alarms': Permission(
u'alarms',
_lazy(u'Gives the extension access to the chrome.alarms API.'),
_lazy(u'Gives the extension access to the chrome.alarms API.')),
u'<all_urls>': ALL_URLS_PERMISSION,
u'http://*/*': ALL_URLS_PERMISSION,
u'https://*/*': ALL_URLS_PERMISSION,
u'*://*/*': ALL_URLS_PERMISSION,
u'bookmarks': Permission(
u'bookmarks',
_lazy(u'Read and modify bookmarks.'),
_lazy(u'Read and modify bookmarks.')),
_lazy(u'Access bookmarks'),
''),
u'clipboard': Permission(
u'clipboard',
_lazy(u'Access text clipboard'),
''),
u'downloads': Permission(
u'downloads',
_lazy(u"Download files and read and modify the browser's download"
u"history"),
''),
u'history': Permission(
u'history',
_lazy(u'Access browser history'),
''),
u'nativeMessaging': Permission(
u'nativeMessaging',
_lazy(u'Exchange messages with programs other than Firefox'),
''),
u'notifications': Permission(
u'notifications',
_lazy(u'Display notifications to you'),
''),
u'sessions': Permission(
u'sessions',
_lazy(u'Access browser history to restore tabs'),
''),
u'tabs': Permission(
u'tabs',
_lazy(u'Access browser tabs'),
''),
u'topSites': Permission(
u'topSites',
_lazy(u'Access browsing history'),
''),
u'webNavigation': Permission(
u'webNavigation',
_lazy(u'Access browser activity during navigation'),
''),
u'unlimitedStorage': Permission(
u'unlimitedStorage',
_lazy(u'Provide unlimited storage of client-side data'),
''),
u'webRequest': Permission(
u'webRequest',
_lazy(u'Access browser during Web activity'),
''),
}
# webRequestBlocking has the same description as webRequest.
WEBEXT_PERMISSIONS[u'webRequestBlocking'] = WEBEXT_PERMISSIONS[u'webRequest']

Просмотреть файл

@ -23,7 +23,7 @@ from olympia.amo.tests import TestCase
from olympia.addons.models import (
Addon, AddonFeatureCompatibility, Charity)
from olympia.amo.helpers import url as url_reverse
from olympia.amo.tests import addon_factory, version_factory
from olympia.amo.tests import addon_factory, user_factory, version_factory
from olympia.amo.tests.test_helpers import get_image_path
from olympia.amo.urlresolvers import reverse
from olympia.api.models import APIKey, SYMMETRIC_JWT_TYPE
@ -1687,6 +1687,34 @@ class TestUploadDetail(BaseUploadTest):
{u'tier': 1, u'message': u'You cannot submit this type of add-on',
u'fatal': True, u'type': u'error'}]
@mock.patch('olympia.devhub.tasks.run_validator')
def test_system_addon_allowed(self, mock_validator):
user_factory(email='redpanda@mozilla.com')
assert self.client.login(email='redpanda@mozilla.com')
mock_validator.return_value = json.dumps(self.validation_ok())
self.upload_file(
'../../../files/fixtures/files/mozilla_guid.xpi')
upload = FileUpload.objects.get()
response = self.client.get(reverse('devhub.upload_detail',
args=[upload.uuid.hex, 'json']))
data = json.loads(response.content)
assert data['validation']['messages'] == []
@mock.patch('olympia.devhub.tasks.run_validator')
def test_system_addon_not_allowed_not_mozilla(self, mock_validator):
user_factory(email='bluepanda@notzilla.com')
assert self.client.login(email='bluepanda@notzilla.com')
self.upload_file(
'../../../files/fixtures/files/mozilla_guid.xpi')
upload = FileUpload.objects.get()
response = self.client.get(reverse('devhub.upload_detail',
args=[upload.uuid.hex, 'json']))
data = json.loads(response.content)
assert data['validation']['messages'] == [
{u'tier': 1, u'message': u'You cannot submit an add-on with a '
u'guid ending "@mozilla.org"',
u'fatal': True, u'type': u'error'}]
def test_no_redirect_for_metadata(self):
user = UserProfile.objects.get(email='regular@mozilla.com')
addon = addon_factory(status=amo.STATUS_NULL)

Просмотреть файл

@ -52,6 +52,7 @@ from olympia.lib.crypto.packaged import sign_file
from olympia.search.views import BaseAjaxSearch
from olympia.translations.models import delete_translation
from olympia.users.models import UserProfile
from olympia.users.utils import system_addon_submission_allowed
from olympia.versions.models import Version
from olympia.zadmin.models import get_config, ValidationResult
@ -804,6 +805,10 @@ def json_upload_detail(request, upload, addon_slug=None):
if not acl.submission_allowed(request.user, pkg):
raise django_forms.ValidationError(
_(u'You cannot submit this type of add-on'))
if not system_addon_submission_allowed(request.user, pkg):
raise django_forms.ValidationError(
_(u'You cannot submit an add-on with a guid ending '
u'"@mozilla.org"'))
except django_forms.ValidationError, exc:
errors_before = result['validation'].get('errors', 0)
# FIXME: This doesn't guard against client-side
@ -1155,7 +1160,7 @@ def version_edit(request, addon_id, addon, version_id):
version.update(has_info_request=False)
if waffle.switch_is_active('activity-email'):
log_and_notify(amo.LOG.APPROVAL_NOTES_CHANGED,
u'Approval notes were updated.',
None,
request.user,
version)
else:
@ -1172,27 +1177,15 @@ def version_edit(request, addon_id, addon, version_id):
version.update(has_info_request=False)
if waffle.switch_is_active('activity-email'):
log_and_notify(amo.LOG.SOURCE_CODE_UPLOADED,
u'Source code was uploaded.',
None,
request.user,
version)
else:
amo.log(amo.LOG.SOURCE_CODE_UPLOADED,
version,
request.user,
details={
'comments': (u'This version has been '
u'automatically flagged for '
u'admin review, as source files '
u'have been uploaded.')})
addon, version, request.user)
else:
amo.log(amo.LOG.SOURCE_CODE_UPLOADED,
version,
request.user,
details={
'comments': (u'This version has been '
u'automatically flagged for '
u'admin review, as source files '
u'have been uploaded.')})
addon, version, request.user)
messages.success(request, _('Changes successfully saved.'))
return redirect('devhub.versions.edit', addon.slug, version_id)
@ -1688,7 +1681,7 @@ def request_review(request, addon_id, addon):
messages.success(request, _('Review requested.'))
else:
messages.success(request, _(
'Review requested. You must provide further details to proceed.'))
'You must provide further details to proceed.'))
amo.log(amo.LOG.CHANGE_STATUS, addon.get_status_display(), addon)
return redirect(addon.get_dev_url('versions'))

Двоичные данные
src/olympia/files/fixtures/files/mozilla_guid.xpi Normal file

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Просмотреть файл

@ -13,7 +13,7 @@ from django.db import models
from django.dispatch import receiver
from django.template.defaultfilters import slugify
from django.utils.encoding import force_bytes, force_text
from django.utils.translation import ugettext as _
import commonware
from cache_nuggets.lib import memoize
from django_extensions.db.fields.json import JSONField
@ -377,20 +377,45 @@ class File(OnChangeMixin, ModelBase):
statsd.timing('files.extract.localepicker', (end * 1000))
return res
@amo.cached_property
@property
def webext_permissions(self):
try:
return {
name: WEBEXT_PERMISSIONS.get(name, Permission(name, '', ''))
for name in self._webext_permissions.permissions}
except WebextPermission.DoesNotExist:
return {}
"""Return permissions with descriptions in defined order:
1) match all urls (e.g. <all-urls>)
2) known permissions, in constants order (alphabetically),
3) match urls for sites
4) unknown permissions
"""
out, urls, unknowns = [], [], []
for name in self.webext_permissions_list:
perm = WEBEXT_PERMISSIONS.get(name, None)
if perm:
# Add known permissions, including match-alls.
if perm not in out:
# We don't want duplicates.
out.append(perm)
elif '//' in name:
# Filter out match urls so we can group them.
urls.append(name)
else:
# Other strings are unknown permissions.
unknowns.append(name)
out.sort()
# TODO: group match urls.
out += [
Permission(name, _(u'Access your data for {name} website').format(
name=name), '')
for name in urls]
# return + other (unknown) permissions at the end.
return out + [Permission(name, name, '') for name in unknowns]
@amo.cached_property
def webext_permissions_known(self):
return {name: perm
for name, perm in self.webext_permissions.iteritems()
if name in WEBEXT_PERMISSIONS}
@amo.cached_property(writable=True)
def webext_permissions_list(self):
if not self.is_webextension:
return []
try:
return list(self._webext_permissions.permissions)
except WebextPermission.DoesNotExist:
return []
@receiver(models.signals.post_save, sender=File,

Просмотреть файл

@ -31,17 +31,17 @@ class TestWebextExtractPermissions(UploadTest):
file_ = File.from_upload(upload, self.version, self.platform,
parsed_data=parsed_data)
assert WebextPermission.objects.count() == 0
assert file_.webext_permissions == {}
assert file_.webext_permissions_list == []
call_command('extract_permissions')
file_ = File.objects.no_cache().get(id=file_.id)
assert WebextPermission.objects.get(file=file_)
permissions = file_.webext_permissions
assert len(permissions) == 3
assert permissions['alarms']
assert permissions[u'http://*/*']
assert permissions[u'https://*/*']
permissions_list = file_.webext_permissions_list
assert len(permissions_list) == 5
assert permissions_list == [u'http://*/*', u'https://*/*', 'bookmarks',
'made up permission', 'https://google.com/'
]
def test_force_extract(self):
upload = self.get_upload('webextension_no_id.xpi')
@ -51,11 +51,10 @@ class TestWebextExtractPermissions(UploadTest):
file_ = File.from_upload(upload, self.version, self.platform,
parsed_data=parsed_data)
assert WebextPermission.objects.count() == 1
assert len(file_.webext_permissions) == 2
assert len(file_.webext_permissions_list) == 4
call_command('extract_permissions', force=True)
file_ = File.objects.no_cache().get(id=file_.id)
assert WebextPermission.objects.get(file=file_)
permissions = file_.webext_permissions
assert len(permissions) == 3
assert len(file_.webext_permissions_list) == 5

Просмотреть файл

@ -20,6 +20,7 @@ from olympia.amo.tests import TestCase
from olympia.amo.utils import rm_local_tmp_dir, chunked
from olympia.addons.models import Addon
from olympia.applications.models import AppVersion
from olympia.constants.webext_permissions import ALL_URLS_PERMISSION
from olympia.files.models import (
EXTENSIONS, File, FileUpload, FileValidation, nfd_str,
track_file_status_change,
@ -243,6 +244,32 @@ class TestFile(TestCase, amo.tests.AMOPaths):
addon.update(status=amo.STATUS_DELETED)
assert f.addon.id == addon_id
def _check_permissions_order(self, permissions):
# We have two match-all urls - no dupes!
assert len(permissions) == 4
# First should be catch-all match urls, if present.
assert permissions[0] == ALL_URLS_PERMISSION
# Second should be known permission(s).
assert permissions[1] == (u'bookmarks', 'Access bookmarks', '')
# Third is match urls for specified site(s).
assert permissions[2] == (u'https://google.com/',
u'Access your data for https://google.com/ '
u'website', '')
# Lastly any unknown permission strings.
assert permissions[3] == (u'made up permission', u'made up permission',
'')
def test_webext_permissions(self):
perm_list = [u'http://*/*', u'<all_urls>', u'bookmarks',
u'made up permission', u'https://google.com/']
file_ = File.objects.get(pk=67442)
file_.webext_permissions_list = perm_list
self._check_permissions_order(file_.webext_permissions)
# Check the order isn't dependent on the order in the manifest
file_.webext_permissions_list.reverse()
self._check_permissions_order(file_.webext_permissions)
class TestTrackFileStatusChange(TestCase):
@ -332,7 +359,8 @@ class TestParseXpi(TestCase):
parsed = self.parse(filename='webextension_no_id.xpi')
assert len(parsed['permissions'])
assert parsed['permissions'] == [
u'http://*/*', u'https://*/*', u'alarms']
u'http://*/*', u'https://*/*', u'bookmarks', u'made up permission',
u'https://google.com/']
def test_parse_apps(self):
exp = (amo.FIREFOX,
@ -1086,22 +1114,16 @@ class TestFileFromUpload(UploadTest):
def test_permissions(self):
upload = self.upload('webextension_no_id.xpi')
parsed_data = parse_addon(upload)
# 5 total permissions.
assert len(parsed_data['permissions']) == 5
file_ = File.from_upload(upload, self.version, self.platform,
parsed_data=parse_addon(upload))
permissions = file_.webext_permissions
assert len(permissions) == 3
# One permission is known so will have a description, etc.
alarm = permissions['alarms']
assert alarm.description == (
u'Gives the extension access to the chrome.alarms API.')
assert u'Gives the extension access to the chrome.alarms API.' in (
alarm.long_description)
known_permissions = file_.webext_permissions_known
assert len(known_permissions) == 1
known_permissions[0] = alarm
# The other two permissions stored are just urls.
assert permissions[u'http://*/*']
assert permissions[u'https://*/*']
parsed_data=parsed_data)
permissions_list = file_.webext_permissions_list
assert len(permissions_list) == 5
assert permissions_list == [u'http://*/*', u'https://*/*', 'bookmarks',
'made up permission', 'https://google.com/'
]
class TestZip(TestCase, amo.tests.AMOPaths):

Просмотреть файл

@ -0,0 +1,3 @@
INSERT INTO waffle_switch (name, active, note, created, modified)
VALUES ('webext-permissions', 0, 'Display permissions popup for webextensions on Add-on detail page.', NOW(), NOW());

Просмотреть файл

@ -0,0 +1,13 @@
from django.conf.urls import include, url
from rest_framework.routers import SimpleRouter
from olympia.reviews.views import ReviewViewSet
reviews = SimpleRouter()
reviews.register(r'review', ReviewViewSet)
urlpatterns = [
url(r'', include(reviews.urls))
]

Просмотреть файл

@ -48,10 +48,16 @@ class BaseReviewSerializer(serializers.ModelSerializer):
data['user_responsible'] = request.user
# There are a few fields that need to be set at creation time and never
# modified afterwards:
if not self.partial:
# Get the add-on pk from the URL, no need to pass it as POST data
# since the URL is always going to have it.
# Because we want to avoid extra queries, addon is a
# SerializerMethodField, which means it needs to be validated
# manually. Fortunately the view does most of the work for us.
data['addon'] = self.context['view'].get_addon_object()
if data['addon'] is None:
raise serializers.ValidationError(
{'addon': _('This field is required.')})
# Get the user from the request, don't allow clients to pick one
# themselves.
@ -59,6 +65,12 @@ class BaseReviewSerializer(serializers.ModelSerializer):
# Also include the user ip adress.
data['ip_address'] = request.META.get('REMOTE_ADDR', '')
else:
# When editing, you can't change the add-on.
if self.context['request'].data.get('addon'):
raise serializers.ValidationError(
{'addon': _(u"You can't change the add-on of a review once"
u" it has been created.")})
# Clean up body and automatically flag the review if an URL was in it.
body = data.get('body', '')
@ -121,7 +133,6 @@ class ReviewVersionSerializer(SimpleVersionSerializer):
class ReviewSerializer(BaseReviewSerializer):
reply = ReviewSerializerReply(read_only=True)
rating = serializers.IntegerField(min_value=1, max_value=5)
version = ReviewVersionSerializer()
class Meta:
@ -136,6 +147,10 @@ class ReviewSerializer(BaseReviewSerializer):
u"the review has been created."))
addon = self.context['view'].get_addon_object()
if not addon:
# BaseReviewSerializer.validate() should complain about that, not
# this method.
return None
if version.addon_id != addon.pk or not version.is_public():
raise serializers.ValidationError(
_(u"This version of the add-on doesn't exist or isn't "

Просмотреть файл

@ -7,8 +7,6 @@ from django.core.urlresolvers import reverse
import mock
from pyquery import PyQuery as pq
from rest_framework.exceptions import ParseError
from rest_framework.test import APIRequestFactory
from olympia import amo
from olympia.addons.utils import generate_addon_guid
@ -19,7 +17,6 @@ from olympia.amo.tests import (
from olympia.access.models import Group, GroupUser
from olympia.addons.models import Addon, AddonUser
from olympia.devhub.models import ActivityLog
from olympia.reviews.views import ReviewViewSet
from olympia.reviews.models import Review, ReviewFlag
from olympia.users.models import UserProfile
@ -718,10 +715,9 @@ class TestReviewViewSetGet(TestCase):
def setUp(self):
self.addon = addon_factory(
guid=generate_addon_guid(), name=u'My Addôn', slug='my-addon')
self.url = reverse(
'addon-review-list', kwargs={'addon_pk': self.addon.pk})
self.url = reverse('review-list')
def test_list(self, **kwargs):
def test_list_addon(self, **kwargs):
review1 = Review.objects.create(
addon=self.addon, body='review 1', user=user_factory(),
rating=1)
@ -755,7 +751,9 @@ class TestReviewViewSetGet(TestCase):
assert Review.unfiltered.count() == 6
response = self.client.get(self.url, kwargs)
params = {'addon': self.addon.pk}
params.update(kwargs)
response = self.client.get(self.url, params)
assert response.status_code == 200
data = json.loads(response.content)
assert data['count'] == 2
@ -765,7 +763,7 @@ class TestReviewViewSetGet(TestCase):
assert data['results'][1]['id'] == review1.pk
return data
def test_list_queries(self):
def test_list_addon_queries(self):
version1 = self.addon.current_version
version2 = version_factory(addon=self.addon)
review1 = Review.objects.create(
@ -800,7 +798,7 @@ class TestReviewViewSetGet(TestCase):
with mock.patch('olympia.reviews.views.ReviewViewSet'
'.get_addon_object') as get_addon_object:
get_addon_object.return_value = self.addon
response = self.client.get(self.url)
response = self.client.get(self.url, {'addon': self.addon.pk})
assert response.status_code == 200
data = json.loads(response.content)
assert data['count'] == 3
@ -810,7 +808,7 @@ class TestReviewViewSetGet(TestCase):
assert data['results'][1]['body'] == review2.body
assert data['results'][2]['body'] == review1.body
def test_list_queries_with_replies(self):
def test_list_addon_queries_with_replies(self):
version1 = self.addon.current_version
version2 = version_factory(addon=self.addon)
review1 = Review.objects.create(
@ -852,7 +850,7 @@ class TestReviewViewSetGet(TestCase):
with mock.patch('olympia.reviews.views.ReviewViewSet'
'.get_addon_object') as get_addon_object:
get_addon_object.return_value = self.addon
response = self.client.get(self.url)
response = self.client.get(self.url, {'addon': self.addon.pk})
assert response.status_code == 200
data = json.loads(response.content)
assert data['count'] == 3
@ -865,40 +863,34 @@ class TestReviewViewSetGet(TestCase):
assert data['results'][2]['body'] == review1.body
assert data['results'][2]['reply']['body'] == reply1.body
def test_list_grouped_ratings(self):
data = self.test_list(show_grouped_ratings=1)
def test_list_addon_grouped_ratings(self):
data = self.test_list_addon(show_grouped_ratings=1)
assert data['grouped_ratings']['1'] == 1
assert data['grouped_ratings']['2'] == 1
assert data['grouped_ratings']['3'] == 0
assert data['grouped_ratings']['4'] == 0
assert data['grouped_ratings']['5'] == 0
def test_list_unknown_addon(self, **kwargs):
self.url = reverse(
'addon-review-list', kwargs={'addon_pk': self.addon.pk + 42})
response = self.client.get(self.url, kwargs)
def test_list_addon_unknown(self, **kwargs):
params = {'addon': self.addon.pk + 42}
params.update(kwargs)
response = self.client.get(self.url, params)
assert response.status_code == 404
data = json.loads(response.content)
return data
def test_list_grouped_ratings_unknown_addon_not_present(self):
data = self.test_list_unknown_addon(show_grouped_ratings=1)
def test_list_addon_grouped_ratings_unknown_addon_not_present(self):
data = self.test_list_addon_unknown(show_grouped_ratings=1)
assert 'grouped_ratings' not in data
def test_list_addon_guid(self):
self.url = reverse(
'addon-review-list', kwargs={'addon_pk': self.addon.guid})
self.test_list()
self.test_list_addon(addon_pk=self.addon.guid)
def test_list_addon_slug(self):
self.url = reverse(
'addon-review-list', kwargs={'addon_pk': self.addon.slug})
self.test_list()
self.test_list_addon(addon_pk=self.addon.slug)
def test_list_user(self, **kwargs):
self.user = user_factory()
self.url = reverse(
'account-review-list', kwargs={'account_pk': self.user.pk})
review1 = Review.objects.create(
addon=self.addon, body='review 1', user=self.user)
review2 = Review.objects.create(
@ -920,7 +912,9 @@ class TestReviewViewSetGet(TestCase):
assert Review.unfiltered.count() == 5
response = self.client.get(self.url, kwargs)
params = {'user': self.user.pk}
params.update(kwargs)
response = self.client.get(self.url, params)
assert response.status_code == 200
data = json.loads(response.content)
assert data['count'] == 3
@ -931,26 +925,55 @@ class TestReviewViewSetGet(TestCase):
assert data['results'][2]['id'] == review2.pk
return data
def test_list_addon_and_user(self):
self.user = user_factory()
old_review = Review.objects.create(
addon=self.addon, body='old review', user=self.user)
old_review.update(created=self.days_ago(42))
recent_review = Review.objects.create(
addon=self.addon, body='recent review', user=self.user)
# None of those extra reviews should show up.
review_deleted = Review.objects.create(
addon=self.addon, body='review deleted', user=self.user)
review_deleted.delete()
other_review = Review.objects.create(
addon=addon_factory(), body='review from other user',
user=user_factory())
Review.objects.create(
addon=other_review.addon, body='reply to other user',
reply_to=other_review, user=self.user) # right user, wrong addon.
Review.objects.create(
addon=addon_factory(), body='review from other addon',
user=self.user)
assert Review.unfiltered.count() == 6
# Since we're filtering on both addon and user, only the most recent
# review from self.user on self.addon should show up.
params = {'addon': self.addon.pk, 'user': self.user.pk}
response = self.client.get(self.url, params)
assert response.status_code == 200
data = json.loads(response.content)
assert data['count'] == 1
assert data['results']
assert len(data['results']) == 1
assert data['results'][0]['id'] == recent_review.pk
def test_list_user_grouped_ratings_not_present(self):
return
data = self.test_list_user(show_grouped_ratings=1)
assert 'grouped_ratings' not in data
def test_list_no_user_or_addon(self):
# We have a fallback in get_queryset() to avoid listing all reviews on
# the website if somehow we messed up the if conditions. It should not
# be possible to reach it, but test it by forcing the instantiation of
# the viewset with no kwargs other than action='list'.
view = ReviewViewSet(action='list', kwargs={},
request=APIRequestFactory().get('/'))
with self.assertRaises(ParseError):
view.filter_queryset(view.get_queryset())
def test_list_no_addon_or_user_present(self):
response = self.client.get(self.url)
assert response.status_code == 400
data = json.loads(response.content)
assert data['detail'] == 'Need an addon or user parameter'
def test_detail(self):
review = Review.objects.create(
addon=self.addon, body='review 1', user=user_factory())
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': review.pk})
self.url = reverse('review-detail', kwargs={'pk': review.pk})
response = self.client.get(self.url)
assert response.status_code == 200
@ -963,9 +986,7 @@ class TestReviewViewSetGet(TestCase):
reply = Review.objects.create(
addon=self.addon, body='reply to review', user=user_factory(),
reply_to=review)
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': reply.pk})
self.url = reverse('review-detail', kwargs={'pk': reply.pk})
response = self.client.get(self.url)
assert response.status_code == 200
@ -975,9 +996,7 @@ class TestReviewViewSetGet(TestCase):
def test_detail_deleted(self):
review = Review.objects.create(
addon=self.addon, body='review 1', user=user_factory())
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': review.pk})
self.url = reverse('review-detail', kwargs={'pk': review.pk})
review.delete()
response = self.client.get(self.url)
@ -990,9 +1009,7 @@ class TestReviewViewSetGet(TestCase):
addon=self.addon, body='reply to review', user=user_factory(),
reply_to=review)
reply.delete()
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': review.pk})
self.url = reverse('review-detail', kwargs={'pk': review.pk})
response = self.client.get(self.url)
assert response.status_code == 200
@ -1011,9 +1028,7 @@ class TestReviewViewSetGet(TestCase):
reply_to=review)
reply.delete()
review.delete()
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': review.pk})
self.url = reverse('review-detail', kwargs={'pk': review.pk})
response = self.client.get(self.url)
assert response.status_code == 200
@ -1050,7 +1065,7 @@ class TestReviewViewSetGet(TestCase):
assert Review.unfiltered.count() == 6
response = self.client.get(self.url)
response = self.client.get(self.url, {'addon': self.addon.pk})
assert response.status_code == 200
data = json.loads(response.content)
assert data['count'] == 2
@ -1091,7 +1106,8 @@ class TestReviewViewSetGet(TestCase):
assert Review.unfiltered.count() == 6
response = self.client.get(self.url, data={'filter': 'with_deleted'})
response = self.client.get(
self.url, {'addon': self.addon.pk, 'filter': 'with_deleted'})
assert response.status_code == 200
data = json.loads(response.content)
assert data['count'] == 3
@ -1115,9 +1131,7 @@ class TestReviewViewSetDelete(TestCase):
self.review = Review.objects.create(
addon=self.addon, version=self.addon.current_version, rating=1,
body='My review', user=self.user)
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': self.review.pk})
self.url = reverse('review-detail', kwargs={'pk': self.review.pk})
def test_delete_anonymous(self):
response = self.client.delete(self.url)
@ -1170,9 +1184,7 @@ class TestReviewViewSetDelete(TestCase):
reply = Review.objects.create(
addon=self.addon, reply_to=self.review,
body=u'Reply that will be delêted...', user=addon_author)
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': reply.pk})
self.url = reverse('review-detail', kwargs={'pk': reply.pk})
response = self.client.delete(self.url)
assert response.status_code == 204
@ -1181,9 +1193,7 @@ class TestReviewViewSetDelete(TestCase):
def test_delete_404(self):
self.client.login_api(self.user)
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': self.review.pk + 42})
self.url = reverse('review-detail', kwargs={'pk': self.review.pk + 42})
response = self.client.delete(self.url)
assert response.status_code == 404
assert Review.objects.count() == 1
@ -1199,9 +1209,7 @@ class TestReviewViewSetEdit(TestCase):
self.review = Review.objects.create(
addon=self.addon, version=self.addon.current_version, rating=1,
body=u'My revïew', title=u'Titlé', user=self.user)
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': self.review.pk})
self.url = reverse('review-detail', kwargs={'pk': self.review.pk})
def test_edit_anonymous(self):
response = self.client.patch(self.url, {'body': u'løl!'})
@ -1268,6 +1276,15 @@ class TestReviewViewSetEdit(TestCase):
u"You can't change the version of the add-on reviewed once "
u"the review has been created."]
def test_edit_dont_allow_addon_to_be_edited(self):
self.client.login_api(self.user)
new_addon = addon_factory()
response = self.client.patch(self.url, {'addon': new_addon.pk})
assert response.status_code == 400
assert response.data['addon'] == [
u"You can't change the add-on of a review once it has been "
u"created."]
def test_edit_admin(self):
original_review_user = self.review.user
admin_user = user_factory(username='mylittleadmin')
@ -1300,9 +1317,7 @@ class TestReviewViewSetEdit(TestCase):
reply = Review.objects.create(
reply_to=self.review, body=u'This is â reply', user=addon_author,
addon=self.addon)
self.url = reverse(
'addon-review-detail',
kwargs={'addon_pk': self.addon.pk, 'pk': reply.pk})
self.url = reverse('review-detail', kwargs={'pk': reply.pk})
response = self.client.patch(self.url, {'rating': 5})
assert response.status_code == 200
@ -1326,20 +1341,31 @@ class TestReviewViewSetPost(TestCase):
def setUp(self):
self.addon = addon_factory(
guid=generate_addon_guid(), name=u'My Addôn', slug='my-addon')
self.url = reverse(
'addon-review-list', kwargs={'addon_pk': self.addon.pk})
self.url = reverse('review-list')
def test_post_anonymous(self):
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5})
assert response.status_code == 401
def test_post_no_addon(self):
self.user = user_factory()
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': self.addon.current_version.pk})
assert response.status_code == 400
assert response.data['addon'] == [u'This field is required.']
def test_post_no_version(self):
self.user = user_factory()
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5})
assert response.status_code == 400
assert response.data['version'] == [u'This field is required.']
@ -1348,8 +1374,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': self.addon.current_version.version})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5, 'version': self.addon.current_version.version})
assert response.status_code == 400
assert response.data['version'] == [
'Incorrect type. Expected pk value, received unicode.']
@ -1361,8 +1387,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': u'blahé', 'rating': 5,
'version': self.addon.current_version.pk},
'addon': self.addon.pk, 'body': u'test bodyé', 'title': u'blahé',
'rating': 5, 'version': self.addon.current_version.pk},
REMOTE_ADDR='213.225.312.5')
assert response.status_code == 201
review = Review.objects.latest('pk')
@ -1398,8 +1424,8 @@ class TestReviewViewSetPost(TestCase):
body = u'Trying to spam <br> http://éxample.com'
cleaned_body = u'Trying to spam \n http://éxample.com'
response = self.client.post(self.url, {
'body': body, 'title': u'blahé', 'rating': 5,
'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'body': body, 'title': u'blahé',
'rating': 5, 'version': self.addon.current_version.pk})
assert response.status_code == 201
review = Review.objects.latest('pk')
assert review.pk == response.data['id']
@ -1422,8 +1448,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 4.5,
'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 4.5, 'version': self.addon.current_version.pk})
assert response.status_code == 400
assert response.data['rating'] == ['A valid integer is required.']
@ -1432,8 +1458,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 6,
'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 6, 'version': self.addon.current_version.pk})
assert response.status_code == 400
assert response.data['rating'] == [
'Ensure this value is less than or equal to 5.']
@ -1443,8 +1469,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 0,
'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 0, 'version': self.addon.current_version.pk})
assert response.status_code == 400
assert response.data['rating'] == [
'Ensure this value is greater than or equal to 1.']
@ -1454,8 +1480,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5, 'version': self.addon.current_version.pk})
assert response.status_code == 201
review = Review.objects.latest('pk')
assert review.pk == response.data['id']
@ -1477,7 +1503,7 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': None, 'title': None, 'rating': 5,
'addon': self.addon.pk, 'body': None, 'title': None, 'rating': 5,
'version': self.addon.current_version.pk})
assert response.status_code == 201
review = Review.objects.latest('pk')
@ -1501,7 +1527,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'rating': 5, 'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'rating': 5,
'version': self.addon.current_version.pk})
assert response.status_code == 201
review = Review.objects.latest('pk')
assert review.pk == response.data['id']
@ -1524,30 +1551,26 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None,
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'version': self.addon.current_version.pk})
assert response.status_code == 400
assert response.data['rating'] == ['This field is required.']
def test_post_no_such_addon_id(self):
self.url = reverse(
'addon-review-list', kwargs={'addon_pk': self.addon.pk + 42})
self.user = user_factory()
self.client.login_api(self.user)
response = self.client.post(self.url, {
'body': 'test body', 'title': None, 'rating': 5,
'version': self.addon.current_version.pk})
'addon': self.addon.pk + 42, 'body': 'test body', 'title': None,
'rating': 5, 'version': self.addon.current_version.pk})
assert response.status_code == 404
def test_post_version_not_linked_to_the_right_addon(self):
addon2 = addon_factory()
self.url = reverse(
'addon-review-list', kwargs={'addon_pk': self.addon.pk})
self.user = user_factory()
self.client.login_api(self.user)
response = self.client.post(self.url, {
'body': 'test body', 'title': None, 'rating': 5,
'version': addon2.current_version.pk})
'addon': self.addon.pk, 'body': 'test body', 'title': None,
'rating': 5, 'version': addon2.current_version.pk})
assert response.status_code == 400
assert response.data['version'] == [
u"This version of the add-on doesn't exist or isn't public."]
@ -1559,8 +1582,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': version_pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5, 'version': version_pk})
assert response.status_code == 404
def test_post_deleted_version(self):
@ -1577,8 +1600,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': old_version_pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5, 'version': old_version_pk})
assert response.status_code == 400
assert response.data['version'] == [
u"This version of the add-on doesn't exist or isn't public."]
@ -1595,8 +1618,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': old_version.pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5, 'version': old_version.pk})
assert response.status_code == 400
assert response.data['version'] == [
u"This version of the add-on doesn't exist or isn't public."]
@ -1608,8 +1631,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': version_pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5, 'version': version_pk})
assert response.status_code == 403
def test_post_logged_in_but_is_addon_author(self):
@ -1618,8 +1641,8 @@ class TestReviewViewSetPost(TestCase):
self.client.login_api(self.user)
assert not Review.objects.exists()
response = self.client.post(self.url, {
'body': u'test bodyé', 'title': None, 'rating': 5,
'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'body': u'test bodyé', 'title': None,
'rating': 5, 'version': self.addon.current_version.pk})
assert response.status_code == 400
assert response.data['non_field_errors'] == [
"You can't leave a review on your own add-on."]
@ -1632,8 +1655,8 @@ class TestReviewViewSetPost(TestCase):
body='My review', user=self.user)
second_version = version_factory(addon=self.addon)
response = self.client.post(self.url, {
'body': u'My ôther review', 'title': None, 'rating': 2,
'version': second_version.pk})
'addon': self.addon.pk, 'body': u'My ôther review', 'title': None,
'rating': 2, 'version': second_version.pk})
assert response.status_code == 201
assert Review.objects.count() == 2
@ -1645,8 +1668,8 @@ class TestReviewViewSetPost(TestCase):
addon=self.addon, version=self.addon.current_version, rating=1,
body='My review', user=self.user)
response = self.client.post(self.url, {
'body': u'My ôther review', 'title': None, 'rating': 2,
'version': self.addon.current_version.pk})
'addon': self.addon.pk, 'body': u'My ôther review', 'title': None,
'rating': 2, 'version': self.addon.current_version.pk})
assert response.status_code == 400
assert response.data['non_field_errors'] == [
u"You can't leave more than one review for the same version of "
@ -1663,9 +1686,11 @@ class TestReviewViewSetFlag(TestCase):
self.review = Review.objects.create(
addon=self.addon, version=self.addon.current_version, rating=1,
body='My review', user=self.review_user)
self.url = reverse(
'addon-review-flag',
kwargs={'addon_pk': self.addon.pk, 'pk': self.review.pk})
self.url = reverse('review-flag', kwargs={'pk': self.review.pk})
def test_url(self):
expected_url = '/api/v3/reviews/review/%d/flag/' % self.review.pk
assert self.url == expected_url
def test_flag_anonymous(self):
response = self.client.post(self.url)
@ -1797,13 +1822,10 @@ class TestReviewViewSetReply(TestCase):
self.review = Review.objects.create(
addon=self.addon, version=self.addon.current_version, rating=1,
body='My review', user=self.review_user)
self.url = reverse(
'addon-review-reply',
kwargs={'addon_pk': self.addon.pk, 'pk': self.review.pk})
self.url = reverse('review-reply', kwargs={'pk': self.review.pk})
def test_url(self):
expected_url = '/api/v3/addons/addon/%d/reviews/%d/reply/' % (
self.addon.pk, self.review.pk)
expected_url = '/api/v3/reviews/review/%d/reply/' % self.review.pk
assert self.url == expected_url
def test_get_method_not_allowed(self):
@ -1826,9 +1848,7 @@ class TestReviewViewSetReply(TestCase):
self.addon_author = user_factory()
self.addon.addonuser_set.create(user=self.addon_author)
self.client.login_api(self.addon_author)
self.url = reverse(
'addon-review-reply',
kwargs={'addon_pk': self.addon.pk, 'pk': self.review.pk + 42})
self.url = reverse('review-reply', kwargs={'pk': self.review.pk + 42})
response = self.client.post(self.url, data={})
assert response.status_code == 404

Просмотреть файл

@ -1,32 +1,28 @@
from django.conf.urls import include, patterns, url
from django.conf.urls import include, url
from olympia.reviews.feeds import ReviewsRss
from . import views
def review_detail_patterns(prefix):
# These all start with /addon/:id/reviews/:review_id/.
return patterns(
'',
url('^$', views.review_list, name='%s.reviews.detail' % prefix),
url('^reply$', views.reply, name='%s.reviews.reply' % prefix),
url('^flag$', views.flag, name='%s.reviews.flag' % prefix),
url('^delete$', views.delete, name='%s.reviews.delete' % prefix),
url('^edit$', views.edit, name='%s.reviews.edit' % prefix),
url('^translate/(?P<language>[a-z]{2,3}(-[A-Z]{2})?)$',
views.translate,
name='%s.reviews.translate' % prefix),
)
# These all start with /addon/:id/reviews/:review_id/.
review_detail_patterns = [
url('^$', views.review_list, name='addons.reviews.detail'),
url('^reply$', views.reply, name='addons.reviews.reply'),
url('^flag$', views.flag, name='addons.reviews.flag'),
url('^delete$', views.delete, name='addons.reviews.delete'),
url('^edit$', views.edit, name='addons.reviews.edit'),
url('^translate/(?P<language>[a-z]{2,3}(-[A-Z]{2})?)$',
views.translate,
name='addons.reviews.translate'),
]
def review_patterns(prefix):
return patterns(
'',
url('^$', views.review_list, name='%s.reviews.list' % prefix),
url('^add$', views.add, name='%s.reviews.add' % prefix),
url('^(?P<review_id>\d+)/', include(review_detail_patterns(prefix))),
url('^format:rss$', ReviewsRss(), name='%s.reviews.list.rss' % prefix),
url('^user:(?P<user_id>\d+)$', views.review_list,
name='%s.reviews.user' % prefix),
)
urlpatterns = [
url('^$', views.review_list, name='addons.reviews.list'),
url('^add$', views.add, name='addons.reviews.add'),
url('^(?P<review_id>\d+)/', include(review_detail_patterns)),
url('^format:rss$', ReviewsRss(), name='addons.reviews.list.rss'),
url('^user:(?P<user_id>\d+)$', views.review_list,
name='addons.reviews.user'),
]

Просмотреть файл

@ -327,9 +327,38 @@ class ReviewViewSet(AddonChildMixin, ModelViewSet):
queryset = Review.objects.all()
def set_addon_object_from_review(self, review):
"""Set addon object on the instance from a review object."""
# At this point it's likely we didn't have an addon in the request, so
# if we went through get_addon_object() before it's going to be set
# to None already. We delete the addon_object property cache and set
# addon_pk in kwargs to force get_addon_object() to reset
# self.addon_object.
del self.addon_object
self.kwargs['addon_pk'] = str(review.addon.pk)
return self.get_addon_object()
def get_addon_object(self):
"""Return addon object associated with the request, or None if not
relevant.
Will also fire permission checks on the addon object when it's loaded.
"""
if hasattr(self, 'addon_object'):
return self.addon_object
if 'addon_pk' not in self.kwargs:
return None
self.kwargs['addon_pk'] = (
self.request.data.get('addon') or
self.request.GET.get('addon'))
if self.kwargs['addon_pk'] is None:
# If we don't have an addon object, set it as None on the instance
# and return immediately, that's fine.
self.addon_object = None
return
else:
# AddonViewSet.get_lookup_field() expects a string.
self.kwargs['addon_pk'] = str(self.kwargs['addon_pk'])
# When loading the add-on, pass a specific permission class - the
# default from AddonViewSet is too restrictive, we are not modifying
# the add-on itself so we don't need all the permission checks it does.
@ -337,12 +366,12 @@ class ReviewViewSet(AddonChildMixin, ModelViewSet):
permission_classes=[AllowIfPublic])
def check_permissions(self, request):
if 'addon_pk' in self.kwargs:
# In addition to the regular permission checks that are made, we
# need to verify that the add-on exists, is public and listed. Just
# loading the addon should be enough to do that, since
# AddonChildMixin implementation calls AddonViewSet.get_object().
self.get_addon_object()
"""Perform permission checks.
The regular DRF permissions checks are made, but also, before that, if
an addon was requested, verify that it exists, is public and listed,
through AllowIfPublic permission, that get_addon_object() uses."""
self.get_addon_object()
# Proceed with the regular permission checks.
return super(ReviewViewSet, self).check_permissions(request)
@ -357,20 +386,21 @@ class ReviewViewSet(AddonChildMixin, ModelViewSet):
def filter_queryset(self, qs):
if self.action == 'list':
if 'addon_pk' in self.kwargs:
if 'addon' in self.request.GET:
qs = qs.filter(is_latest=True, addon=self.get_addon_object())
elif 'account_pk' in self.kwargs:
qs = qs.filter(user=self.kwargs.get('account_pk'))
else:
if 'user' in self.request.GET:
qs = qs.filter(user=self.request.GET.get('user'))
if ('addon' not in self.request.GET and
'user' not in self.request.GET):
# Don't allow listing reviews without filtering by add-on or
# user.
raise ParseError('Need an addon or user identifier')
raise ParseError('Need an addon or user parameter')
return qs
def get_paginated_response(self, data):
response = super(ReviewViewSet, self).get_paginated_response(data)
show_grouped_ratings = self.request.GET.get('show_grouped_ratings')
if 'addon_pk' in self.kwargs and show_grouped_ratings:
if show_grouped_ratings and self.get_addon_object():
response.data['grouped_ratings'] = dict(GroupedRating.get(
self.addon_object.id))
return response
@ -387,7 +417,7 @@ class ReviewViewSet(AddonChildMixin, ModelViewSet):
acl.action_allowed(self.request, 'Addons', 'Edit'))
should_access_only_top_level_reviews = (
self.action == 'list' and self.kwargs.get('addon_pk'))
self.action == 'list' and self.get_addon_object())
if self.should_access_deleted_reviews:
# For admins or add-on authors replying. When listing, we include
@ -423,6 +453,7 @@ class ReviewViewSet(AddonChildMixin, ModelViewSet):
# FK to the current review object and only allow add-on authors/admins.
# Call get_object() to trigger 404 if it does not exist.
self.review_object = self.get_object()
self.set_addon_object_from_review(self.review_object)
if Review.unfiltered.filter(reply_to=self.review_object).exists():
# A reply already exists, just edit it.
# We set should_access_deleted_reviews so that it works even if
@ -434,14 +465,18 @@ class ReviewViewSet(AddonChildMixin, ModelViewSet):
@detail_route(methods=['post'])
def flag(self, request, *args, **kwargs):
# We load the add-on object from the review to trigger permission
# checks.
self.review_object = self.get_object()
self.set_addon_object_from_review(self.review_object)
# Re-use flag view since it's already returning json. We just need to
# pass it the addon slug (passing it the PK would result in a redirect)
# and make sure request.POST is set with whatever data was sent to the
# DRF view.
addon = self.get_addon_object()
request._request.POST = request.data
request = request._request
response = flag(request, addon.slug, kwargs.get('pk'))
response = flag(request, self.addon_object.slug, kwargs.get('pk'))
if response.status_code == 200:
response.content = ''
response.status_code = 202

Просмотреть файл

@ -242,6 +242,42 @@ class TestUploadVersion(BaseUploadVersionCase):
assert response.data['error'] == (
'You cannot submit this type of add-on')
def test_system_addon_allowed(self):
guid = 'systemaddon@mozilla.org'
self.user.update(email='redpanda@mozilla.com')
qs = Addon.unfiltered.filter(guid=guid)
assert not qs.exists()
response = self.request(
'PUT',
addon=guid, version='0.0.1',
filename='src/olympia/files/fixtures/files/'
'mozilla_guid.xpi')
assert response.status_code == 201
assert qs.exists()
addon = qs.get()
assert addon.has_author(self.user)
assert addon.status == amo.STATUS_NULL
latest_version = addon.find_latest_version(
channel=amo.RELEASE_CHANNEL_UNLISTED)
assert latest_version
assert latest_version.channel == amo.RELEASE_CHANNEL_UNLISTED
self.auto_sign_version.assert_called_with(
latest_version, is_beta=False)
def test_system_addon_not_allowed_not_mozilla(self):
guid = 'systemaddon@mozilla.org'
self.user.update(email='yellowpanda@notzilla.com')
qs = Addon.unfiltered.filter(guid=guid)
assert not qs.exists()
response = self.request(
'PUT',
addon=guid, version='0.1',
filename='src/olympia/files/fixtures/files/'
'telemetry_experiment.xpi')
assert response.status_code == 400
assert response.data['error'] == (
'You cannot submit this type of add-on')
def test_version_is_beta_unlisted(self):
addon = Addon.objects.get(guid=self.guid)
self.make_addon_unlisted(addon)

Просмотреть файл

@ -18,6 +18,7 @@ from olympia.api.authentication import JWTKeyAuthentication
from olympia.devhub.views import handle_upload
from olympia.files.models import FileUpload
from olympia.files.utils import parse_addon
from olympia.users.utils import system_addon_submission_allowed
from olympia.versions import views as version_views
from olympia.versions.models import Version
from olympia.signing.serializers import FileUploadSerializer
@ -130,6 +131,12 @@ class VersionView(APIView):
_(u'You cannot submit this type of add-on'),
status.HTTP_400_BAD_REQUEST)
if not system_addon_submission_allowed(request.user, pkg):
raise forms.ValidationError(
_(u'You cannot submit an add-on with a guid ending '
u'"@mozilla.org"'),
status.HTTP_400_BAD_REQUEST)
if addon is not None and addon.status == amo.STATUS_DISABLED:
raise forms.ValidationError(
_('You cannot add versions to an addon that has status: %s.' %

Просмотреть файл

@ -81,3 +81,10 @@ def autocreate_username(candidate, tries=1):
if UserProfile.objects.filter(username=adjusted_u).count():
return autocreate_username(candidate, tries=tries + 1)
return adjusted_u
def system_addon_submission_allowed(user, parsed_addon_data):
guid = parsed_addon_data.get('guid') or ''
return (
not guid.endswith(u'@mozilla.org') or
user.email.endswith(u'@mozilla.com'))

Просмотреть файл

@ -1356,6 +1356,7 @@ h3.author .transfer-ownership {
h4.author a,
a.eula,
a.privacy-policy,
a.webext-permissions,
.notice h3,
.notice p,
.notice p a,
@ -1370,6 +1371,7 @@ h3.author .transfer-ownership {
h4.author a,
a.privacy-policy,
a.webext-permissions,
a.eula,
.vital a,
.warning a,
@ -2571,3 +2573,30 @@ body.windows .install-shell .platform.mac {
}
}
}
.webext-permissions.badge {
img {
height: 1.1em;
}
}
#webext-permissions {
li {
list-style: circle;
list-style-position: inside;
}
.prose {
h3 {
font-size: larger
}
img {
float: left;
margin: 10px;
}
}
}
div.detailed {
display: inline-block;
vertical-align: bottom;
}

Просмотреть файл

@ -159,6 +159,8 @@ $(function () {
if ($('#privacy-policy').exists())
$('#privacy-policy').modal('.privacy-policy', { width: '500px' });
if ($('#webext-permissions').exists())
$('#webext-permissions').modal('.webext-permissions', { width: '500px' });
// Show add-on ID when icon is clicked
if ($("#addon[data-id], #persona[data-id]").exists()) {

Просмотреть файл

@ -12,6 +12,11 @@
// Update the 'Search add-ons for <b>"{addon}"</b>' text.
settings['$results'].find('p b').html(format('"{0}"',
settings.searchTerm));
// Update the .sel link.
var searchUrl = settings['$form'].attr('action') + '?q={0}';
settings['$results'].find('.sel').attr('href', format(searchUrl,
settings.urlSearchTerm));
var li_item = template(
'<li><a href="{url}"><span {cls} {icon}>{name}</span>{subtitle}</a></li>'

Просмотреть файл

@ -130,12 +130,14 @@ $.fn.searchSuggestions = function($results, processCallback, searchType) {
$results.filter('.visible').removeClass('visible');
return;
}
var urlVal = encodeURIComponent($self.val());
// Required data to send to the callback.
var settings = {
'$results': $results,
'$form': $form,
'searchTerm': val
'searchTerm': val,
'urlSearchTerm': urlVal
};
// Optional data for callback.