We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
T - Add contributions to whoami
During a conversation on the https://github.com/mdn/kuma/pull/7188#issuecomment-637707101 for https://github.com/mdn/kuma/issues/7077 it was decided to only show the `Contributions` link in the usernav for users that have made contributions.

Currently, the `whoami` endpoint does not contain this information.

## Acceptance Criteria

- [ ] `whoami` endpoind exposes a `wiki_contributions` (integer) property for authenticated users

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kuma/api/v1/views.py`
Content:
```
1 import json
2 import os
3 from datetime import datetime
4 from urllib.parse import urlparse
5 
6 import stripe
7 from django.conf import settings
8 from django.contrib.auth import get_user_model
9 from django.http import (
10     HttpResponse,
11     HttpResponseBadRequest,
12     JsonResponse,
13 )
14 from django.utils import translation
15 from django.views.decorators.cache import never_cache
16 from django.views.decorators.csrf import csrf_exempt
17 from django.views.decorators.http import require_GET, require_POST
18 from ratelimit.decorators import ratelimit
19 from raven.contrib.django.models import client as raven_client
20 from rest_framework import serializers, status
21 from rest_framework.decorators import api_view
22 from rest_framework.renderers import JSONRenderer
23 from rest_framework.response import Response
24 from waffle import flag_is_active
25 from waffle.decorators import waffle_flag
26 from waffle.models import Flag, Sample, Switch
27 
28 from kuma.api.v1.serializers import BCSignalSerializer
29 from kuma.core.email_utils import render_email
30 from kuma.core.ga_tracking import (
31     ACTION_SUBSCRIPTION_CANCELED,
32     ACTION_SUBSCRIPTION_CREATED,
33     ACTION_SUBSCRIPTION_FEEDBACK,
34     CATEGORY_MONTHLY_PAYMENTS,
35     track_event,
36 )
37 from kuma.core.urlresolvers import reverse
38 from kuma.core.utils import requests_retry_session, send_mail_retrying
39 from kuma.search.filters import (
40     HighlightFilterBackend,
41     KeywordQueryBackend,
42     LanguageFilterBackend,
43     SearchQueryBackend,
44     TagGroupFilterBackend,
45 )
46 from kuma.search.search import SearchView
47 from kuma.users.models import User, UserSubscription
48 from kuma.users.stripe_utils import (
49     cancel_stripe_customer_subscriptions,
50     create_stripe_customer_and_subscription_for_user,
51     retrieve_and_synchronize_subscription_info,
52 )
53 from kuma.users.templatetags.jinja_helpers import get_avatar_url
54 from kuma.wiki.templatetags.jinja_helpers import absolutify
55 
56 
57 @never_cache
58 @require_GET
59 def whoami(request):
60     """
61     Return a JSON object representing the current user, either
62     authenticated or anonymous.
63     """
64     user = request.user
65     if user.is_authenticated:
66         data = {
67             "username": user.username,
68             "is_authenticated": True,
69             "avatar_url": get_avatar_url(user),
70             "email": user.email,
71             "subscriber_number": user.subscriber_number,
72         }
73         if UserSubscription.objects.filter(user=user, canceled__isnull=True).exists():
74             data["is_subscriber"] = True
75         if user.is_staff:
76             data["is_staff"] = True
77         if user.is_superuser:
78             data["is_superuser"] = True
79         if user.is_beta_tester:
80             data["is_beta_tester"] = True
81     else:
82         data = {}
83 
84     # Add waffle data to the dict we're going to be returning.
85     # This is what the waffle.wafflejs() template tag does, but we're
86     # doing it via an API instead of hardcoding the settings into
87     # the HTML page. See also from waffle.views._generate_waffle_js.
88     #
89     # Note that if we upgrade django-waffle, version 15 introduces a
90     # pluggable flag model, and the approved way to get all flag
91     # objects will then become:
92     #    get_waffle_flag_model().get_all()
93     #
94     data["waffle"] = {
95         "flags": {f.name: True for f in Flag.get_all() if f.is_active(request)},
96         "switches": {s.name: True for s in Switch.get_all() if s.is_active()},
97         "samples": {s.name: True for s in Sample.get_all() if s.is_active()},
98     }
99     return JsonResponse(data)
100 
101 
102 class APIDocumentSerializer(serializers.Serializer):
103     title = serializers.CharField(read_only=True, max_length=255)
104     slug = serializers.CharField(read_only=True, max_length=255)
105     locale = serializers.CharField(read_only=True, max_length=7)
106     excerpt = serializers.ReadOnlyField(source="get_excerpt")
107 
108 
109 class APILanguageFilterBackend(LanguageFilterBackend):
110     """Override of kuma.search.filters:LanguageFilterBackend that is almost
111     exactly the same except the locale comes from custom code rather than
112     via kuma.core.i18n.get_language_from_request because that can't be used
113     in the API.
114 
115     Basically, it's the same exact functionality but ...
116     """
117 
118     def filter_queryset(self, request, queryset, view):
119         locale = request.GET.get("locale") or settings.LANGUAGE_CODE
120         if locale not in settings.ACCEPTED_LOCALES:
121             raise serializers.ValidationError({"error": "Not a valid locale code"})
122         request.LANGUAGE_CODE = locale
123         return super(APILanguageFilterBackend, self).filter_queryset(
124             request, queryset, view
125         )
126 
127 
128 class APISearchView(SearchView):
129     serializer_class = APIDocumentSerializer
130     renderer_classes = [JSONRenderer]
131     filter_backends = (
132         SearchQueryBackend,
133         KeywordQueryBackend,
134         TagGroupFilterBackend,
135         APILanguageFilterBackend,
136         HighlightFilterBackend,
137     )
138 
139 
140 search = never_cache(APISearchView.as_view())
141 
142 
143 @ratelimit(key="user_or_ip", rate="10/d", block=True)
144 @api_view(["POST"])
145 def bc_signal(request):
146     if not settings.ENABLE_BCD_SIGNAL:
147         return Response("not enabled", status=status.HTTP_400_BAD_REQUEST)
148 
149     serializer = BCSignalSerializer(data=request.data)
150     if serializer.is_valid():
151         serializer.save()
152         return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
153     return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
154 
155 
156 @waffle_flag("subscription")
157 @never_cache
158 @require_POST
159 def send_subscriptions_feedback(request):
160     """
161     Sends feedback to Google Analytics. This is done on the
162     backend to ensure that all feedback is collected, even
163     from users with DNT or where GA is disabled.
164     """
165     data = json.loads(request.body)
166     feedback = (data.get("feedback") or "").strip()
167 
168     if not feedback:
169         return HttpResponseBadRequest("no feedback")
170 
171     track_event(
172         CATEGORY_MONTHLY_PAYMENTS, ACTION_SUBSCRIPTION_FEEDBACK, data["feedback"]
173     )
174     return HttpResponse(status=204)
175 
176 
177 @api_view(["POST", "GET", "DELETE"])
178 @never_cache
179 def subscriptions(request):
180     if not request.user.is_authenticated or not flag_is_active(request, "subscription"):
181         return Response(None, status=status.HTTP_403_FORBIDDEN)
182 
183     if request.method == "POST":
184         create_stripe_customer_and_subscription_for_user(
185             request.user, request.user.email, request.data["stripe_token"]
186         )
187         return Response(None, status=status.HTTP_201_CREATED)
188     elif request.method == "DELETE":
189         cancelled = cancel_stripe_customer_subscriptions(request.user)
190         if cancelled:
191             return Response(None, status=status.HTTP_204_NO_CONTENT)
192         else:
193             return Response("nothing to cancel", status=status.HTTP_410_GONE)
194 
195     all_subscriptions = []
196     subscription_info = retrieve_and_synchronize_subscription_info(request.user)
197     if subscription_info:
198         all_subscriptions.append(subscription_info)
199 
200     return Response({"subscriptions": all_subscriptions})
201 
202 
203 @csrf_exempt
204 @require_POST
205 @never_cache
206 def stripe_hooks(request):
207     try:
208         payload = json.loads(request.body)
209     except ValueError:
210         return HttpResponseBadRequest("Invalid JSON payload")
211 
212     try:
213         event = stripe.Event.construct_from(payload, stripe.api_key)
214     except stripe.error.StripeError:
215         raven_client.captureException()
216         return HttpResponseBadRequest()
217 
218     # Generally, for this list of if-statements, see the create_missing_stripe_webhook
219     # function.
220     # The list of events there ought to at least minimally match what we're prepared
221     # to deal with here.
222 
223     if event.type == "invoice.payment_succeeded":
224         invoice = event.data.object
225         _send_payment_received_email(invoice, request.LANGUAGE_CODE)
226         track_event(
227             CATEGORY_MONTHLY_PAYMENTS,
228             ACTION_SUBSCRIPTION_CREATED,
229             f"{settings.CONTRIBUTION_AMOUNT_USD:.2f}",
230         )
231 
232     elif event.type == "customer.subscription.deleted":
233         obj = event.data.object
234         for user in User.objects.filter(stripe_customer_id=obj.customer):
235             UserSubscription.set_canceled(user, obj.id)
236         track_event(CATEGORY_MONTHLY_PAYMENTS, ACTION_SUBSCRIPTION_CANCELED, "webhook")
237 
238     else:
239         return HttpResponseBadRequest(
240             f"We did not expect a Stripe webhook of type {event.type!r}"
241         )
242 
243     return HttpResponse()
244 
245 
246 def _send_payment_received_email(invoice, locale):
247     user = get_user_model().objects.get(stripe_customer_id=invoice.customer)
248     subscription_info = retrieve_and_synchronize_subscription_info(user)
249     locale = locale or settings.WIKI_DEFAULT_LANGUAGE
250     context = {
251         "payment_date": datetime.fromtimestamp(invoice.created),
252         "next_payment_date": subscription_info["next_payment_at"],
253         "invoice_number": invoice.number,
254         "cost": invoice.total / 100,
255         "credit_card_brand": subscription_info["brand"],
256         "manage_subscription_url": absolutify(reverse("payment_management")),
257         "faq_url": absolutify(reverse("payments_index")),
258         "contact_email": settings.CONTRIBUTION_SUPPORT_EMAIL,
259     }
260     with translation.override(locale):
261         subject = render_email("users/email/payment_received/subject.ltxt", context)
262         # Email subject *must not* contain newlines
263         subject = "".join(subject.splitlines())
264         plain = render_email("users/email/payment_received/plain.ltxt", context)
265 
266         send_mail_retrying(
267             subject,
268             plain,
269             settings.DEFAULT_FROM_EMAIL,
270             [user.email],
271             attachment={
272                 "name": os.path.basename(urlparse(invoice.invoice_pdf).path),
273                 "bytes": _download_from_url(invoice.invoice_pdf),
274                 "mime": "application/pdf",
275             },
276         )
277 
278 
279 def _download_from_url(url):
280     pdf_download = requests_retry_session().get(url)
281     pdf_download.raise_for_status()
282     return pdf_download.content
283
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kuma/api/v1/views.py b/kuma/api/v1/views.py
--- a/kuma/api/v1/views.py
+++ b/kuma/api/v1/views.py
@@ -78,6 +78,10 @@
             data["is_superuser"] = True
         if user.is_beta_tester:
             data["is_beta_tester"] = True
+
+        # This is rather temporary field. Once we're off the Wiki and into Yari
+        # this no longer makes sense to keep.
+        data["wiki_contributions"] = user.created_revisions.count()
     else:
         data = {}

{"golden_diff": "diff --git a/kuma/api/v1/views.py b/kuma/api/v1/views.py\n--- a/kuma/api/v1/views.py\n+++ b/kuma/api/v1/views.py\n@@ -78,6 +78,10 @@\n             data[\"is_superuser\"] = True\n         if user.is_beta_tester:\n             data[\"is_beta_tester\"] = True\n+\n+        # This is rather temporary field. Once we're off the Wiki and into Yari\n+        # this no longer makes sense to keep.\n+        data[\"wiki_contributions\"] = user.created_revisions.count()\n     else:\n         data = {}\n", "issue": "T - Add contributions to whoami\nDuring a conversation on the https://github.com/mdn/kuma/pull/7188#issuecomment-637707101 for https://github.com/mdn/kuma/issues/7077 it was decided to only show the `Contributions` link in the usernav for users that have made contributions.\r\n\r\nCurrently, the `whoami` endpoint does not contain this information.\r\n\r\n## Acceptance Criteria\r\n\r\n- [ ] `whoami` endpoind exposes a `wiki_contributions` (integer) property for authenticated users\n", "before_files": [{"content": "import json\nimport os\nfrom datetime import datetime\nfrom urllib.parse import urlparse\n\nimport stripe\nfrom django.conf import settings\nfrom django.contrib.auth import get_user_model\nfrom django.http import (\n    HttpResponse,\n    HttpResponseBadRequest,\n    JsonResponse,\n)\nfrom django.utils import translation\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.decorators.http import require_GET, require_POST\nfrom ratelimit.decorators import ratelimit\nfrom raven.contrib.django.models import client as raven_client\nfrom rest_framework import serializers, status\nfrom rest_framework.decorators import api_view\nfrom rest_framework.renderers import JSONRenderer\nfrom rest_framework.response import Response\nfrom waffle import flag_is_active\nfrom waffle.decorators import waffle_flag\nfrom waffle.models import Flag, Sample, Switch\n\nfrom kuma.api.v1.serializers import BCSignalSerializer\nfrom kuma.core.email_utils import render_email\nfrom kuma.core.ga_tracking import (\n    ACTION_SUBSCRIPTION_CANCELED,\n    ACTION_SUBSCRIPTION_CREATED,\n    ACTION_SUBSCRIPTION_FEEDBACK,\n    CATEGORY_MONTHLY_PAYMENTS,\n    track_event,\n)\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.core.utils import requests_retry_session, send_mail_retrying\nfrom kuma.search.filters import (\n    HighlightFilterBackend,\n    KeywordQueryBackend,\n    LanguageFilterBackend,\n    SearchQueryBackend,\n    TagGroupFilterBackend,\n)\nfrom kuma.search.search import SearchView\nfrom kuma.users.models import User, UserSubscription\nfrom kuma.users.stripe_utils import (\n    cancel_stripe_customer_subscriptions,\n    create_stripe_customer_and_subscription_for_user,\n    retrieve_and_synchronize_subscription_info,\n)\nfrom kuma.users.templatetags.jinja_helpers import get_avatar_url\nfrom kuma.wiki.templatetags.jinja_helpers import absolutify\n\n\n@never_cache\n@require_GET\ndef whoami(request):\n    \"\"\"\n    Return a JSON object representing the current user, either\n    authenticated or anonymous.\n    \"\"\"\n    user = request.user\n    if user.is_authenticated:\n        data = {\n            \"username\": user.username,\n            \"is_authenticated\": True,\n            \"avatar_url\": get_avatar_url(user),\n            \"email\": user.email,\n            \"subscriber_number\": user.subscriber_number,\n        }\n        if UserSubscription.objects.filter(user=user, canceled__isnull=True).exists():\n            data[\"is_subscriber\"] = True\n        if user.is_staff:\n            data[\"is_staff\"] = True\n        if user.is_superuser:\n            data[\"is_superuser\"] = True\n        if user.is_beta_tester:\n            data[\"is_beta_tester\"] = True\n    else:\n        data = {}\n\n    # Add waffle data to the dict we're going to be returning.\n    # This is what the waffle.wafflejs() template tag does, but we're\n    # doing it via an API instead of hardcoding the settings into\n    # the HTML page. See also from waffle.views._generate_waffle_js.\n    #\n    # Note that if we upgrade django-waffle, version 15 introduces a\n    # pluggable flag model, and the approved way to get all flag\n    # objects will then become:\n    #    get_waffle_flag_model().get_all()\n    #\n    data[\"waffle\"] = {\n        \"flags\": {f.name: True for f in Flag.get_all() if f.is_active(request)},\n        \"switches\": {s.name: True for s in Switch.get_all() if s.is_active()},\n        \"samples\": {s.name: True for s in Sample.get_all() if s.is_active()},\n    }\n    return JsonResponse(data)\n\n\nclass APIDocumentSerializer(serializers.Serializer):\n    title = serializers.CharField(read_only=True, max_length=255)\n    slug = serializers.CharField(read_only=True, max_length=255)\n    locale = serializers.CharField(read_only=True, max_length=7)\n    excerpt = serializers.ReadOnlyField(source=\"get_excerpt\")\n\n\nclass APILanguageFilterBackend(LanguageFilterBackend):\n    \"\"\"Override of kuma.search.filters:LanguageFilterBackend that is almost\n    exactly the same except the locale comes from custom code rather than\n    via kuma.core.i18n.get_language_from_request because that can't be used\n    in the API.\n\n    Basically, it's the same exact functionality but ...\n    \"\"\"\n\n    def filter_queryset(self, request, queryset, view):\n        locale = request.GET.get(\"locale\") or settings.LANGUAGE_CODE\n        if locale not in settings.ACCEPTED_LOCALES:\n            raise serializers.ValidationError({\"error\": \"Not a valid locale code\"})\n        request.LANGUAGE_CODE = locale\n        return super(APILanguageFilterBackend, self).filter_queryset(\n            request, queryset, view\n        )\n\n\nclass APISearchView(SearchView):\n    serializer_class = APIDocumentSerializer\n    renderer_classes = [JSONRenderer]\n    filter_backends = (\n        SearchQueryBackend,\n        KeywordQueryBackend,\n        TagGroupFilterBackend,\n        APILanguageFilterBackend,\n        HighlightFilterBackend,\n    )\n\n\nsearch = never_cache(APISearchView.as_view())\n\n\n@ratelimit(key=\"user_or_ip\", rate=\"10/d\", block=True)\n@api_view([\"POST\"])\ndef bc_signal(request):\n    if not settings.ENABLE_BCD_SIGNAL:\n        return Response(\"not enabled\", status=status.HTTP_400_BAD_REQUEST)\n\n    serializer = BCSignalSerializer(data=request.data)\n    if serializer.is_valid():\n        serializer.save()\n        return Response(serializer.validated_data, status=status.HTTP_201_CREATED)\n    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n\n\n@waffle_flag(\"subscription\")\n@never_cache\n@require_POST\ndef send_subscriptions_feedback(request):\n    \"\"\"\n    Sends feedback to Google Analytics. This is done on the\n    backend to ensure that all feedback is collected, even\n    from users with DNT or where GA is disabled.\n    \"\"\"\n    data = json.loads(request.body)\n    feedback = (data.get(\"feedback\") or \"\").strip()\n\n    if not feedback:\n        return HttpResponseBadRequest(\"no feedback\")\n\n    track_event(\n        CATEGORY_MONTHLY_PAYMENTS, ACTION_SUBSCRIPTION_FEEDBACK, data[\"feedback\"]\n    )\n    return HttpResponse(status=204)\n\n\n@api_view([\"POST\", \"GET\", \"DELETE\"])\n@never_cache\ndef subscriptions(request):\n    if not request.user.is_authenticated or not flag_is_active(request, \"subscription\"):\n        return Response(None, status=status.HTTP_403_FORBIDDEN)\n\n    if request.method == \"POST\":\n        create_stripe_customer_and_subscription_for_user(\n            request.user, request.user.email, request.data[\"stripe_token\"]\n        )\n        return Response(None, status=status.HTTP_201_CREATED)\n    elif request.method == \"DELETE\":\n        cancelled = cancel_stripe_customer_subscriptions(request.user)\n        if cancelled:\n            return Response(None, status=status.HTTP_204_NO_CONTENT)\n        else:\n            return Response(\"nothing to cancel\", status=status.HTTP_410_GONE)\n\n    all_subscriptions = []\n    subscription_info = retrieve_and_synchronize_subscription_info(request.user)\n    if subscription_info:\n        all_subscriptions.append(subscription_info)\n\n    return Response({\"subscriptions\": all_subscriptions})\n\n\n@csrf_exempt\n@require_POST\n@never_cache\ndef stripe_hooks(request):\n    try:\n        payload = json.loads(request.body)\n    except ValueError:\n        return HttpResponseBadRequest(\"Invalid JSON payload\")\n\n    try:\n        event = stripe.Event.construct_from(payload, stripe.api_key)\n    except stripe.error.StripeError:\n        raven_client.captureException()\n        return HttpResponseBadRequest()\n\n    # Generally, for this list of if-statements, see the create_missing_stripe_webhook\n    # function.\n    # The list of events there ought to at least minimally match what we're prepared\n    # to deal with here.\n\n    if event.type == \"invoice.payment_succeeded\":\n        invoice = event.data.object\n        _send_payment_received_email(invoice, request.LANGUAGE_CODE)\n        track_event(\n            CATEGORY_MONTHLY_PAYMENTS,\n            ACTION_SUBSCRIPTION_CREATED,\n            f\"{settings.CONTRIBUTION_AMOUNT_USD:.2f}\",\n        )\n\n    elif event.type == \"customer.subscription.deleted\":\n        obj = event.data.object\n        for user in User.objects.filter(stripe_customer_id=obj.customer):\n            UserSubscription.set_canceled(user, obj.id)\n        track_event(CATEGORY_MONTHLY_PAYMENTS, ACTION_SUBSCRIPTION_CANCELED, \"webhook\")\n\n    else:\n        return HttpResponseBadRequest(\n            f\"We did not expect a Stripe webhook of type {event.type!r}\"\n        )\n\n    return HttpResponse()\n\n\ndef _send_payment_received_email(invoice, locale):\n    user = get_user_model().objects.get(stripe_customer_id=invoice.customer)\n    subscription_info = retrieve_and_synchronize_subscription_info(user)\n    locale = locale or settings.WIKI_DEFAULT_LANGUAGE\n    context = {\n        \"payment_date\": datetime.fromtimestamp(invoice.created),\n        \"next_payment_date\": subscription_info[\"next_payment_at\"],\n        \"invoice_number\": invoice.number,\n        \"cost\": invoice.total / 100,\n        \"credit_card_brand\": subscription_info[\"brand\"],\n        \"manage_subscription_url\": absolutify(reverse(\"payment_management\")),\n        \"faq_url\": absolutify(reverse(\"payments_index\")),\n        \"contact_email\": settings.CONTRIBUTION_SUPPORT_EMAIL,\n    }\n    with translation.override(locale):\n        subject = render_email(\"users/email/payment_received/subject.ltxt\", context)\n        # Email subject *must not* contain newlines\n        subject = \"\".join(subject.splitlines())\n        plain = render_email(\"users/email/payment_received/plain.ltxt\", context)\n\n        send_mail_retrying(\n            subject,\n            plain,\n            settings.DEFAULT_FROM_EMAIL,\n            [user.email],\n            attachment={\n                \"name\": os.path.basename(urlparse(invoice.invoice_pdf).path),\n                \"bytes\": _download_from_url(invoice.invoice_pdf),\n                \"mime\": \"application/pdf\",\n            },\n        )\n\n\ndef _download_from_url(url):\n    pdf_download = requests_retry_session().get(url)\n    pdf_download.raise_for_status()\n    return pdf_download.content\n", "path": "kuma/api/v1/views.py"}], "after_files": [{"content": "import json\nimport os\nfrom datetime import datetime\nfrom urllib.parse import urlparse\n\nimport stripe\nfrom django.conf import settings\nfrom django.contrib.auth import get_user_model\nfrom django.http import (\n    HttpResponse,\n    HttpResponseBadRequest,\n    JsonResponse,\n)\nfrom django.utils import translation\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.csrf import csrf_exempt\nfrom django.views.decorators.http import require_GET, require_POST\nfrom ratelimit.decorators import ratelimit\nfrom raven.contrib.django.models import client as raven_client\nfrom rest_framework import serializers, status\nfrom rest_framework.decorators import api_view\nfrom rest_framework.renderers import JSONRenderer\nfrom rest_framework.response import Response\nfrom waffle import flag_is_active\nfrom waffle.decorators import waffle_flag\nfrom waffle.models import Flag, Sample, Switch\n\nfrom kuma.api.v1.serializers import BCSignalSerializer\nfrom kuma.core.email_utils import render_email\nfrom kuma.core.ga_tracking import (\n    ACTION_SUBSCRIPTION_CANCELED,\n    ACTION_SUBSCRIPTION_CREATED,\n    ACTION_SUBSCRIPTION_FEEDBACK,\n    CATEGORY_MONTHLY_PAYMENTS,\n    track_event,\n)\nfrom kuma.core.urlresolvers import reverse\nfrom kuma.core.utils import requests_retry_session, send_mail_retrying\nfrom kuma.search.filters import (\n    HighlightFilterBackend,\n    KeywordQueryBackend,\n    LanguageFilterBackend,\n    SearchQueryBackend,\n    TagGroupFilterBackend,\n)\nfrom kuma.search.search import SearchView\nfrom kuma.users.models import User, UserSubscription\nfrom kuma.users.stripe_utils import (\n    cancel_stripe_customer_subscriptions,\n    create_stripe_customer_and_subscription_for_user,\n    retrieve_and_synchronize_subscription_info,\n)\nfrom kuma.users.templatetags.jinja_helpers import get_avatar_url\nfrom kuma.wiki.templatetags.jinja_helpers import absolutify\n\n\n@never_cache\n@require_GET\ndef whoami(request):\n    \"\"\"\n    Return a JSON object representing the current user, either\n    authenticated or anonymous.\n    \"\"\"\n    user = request.user\n    if user.is_authenticated:\n        data = {\n            \"username\": user.username,\n            \"is_authenticated\": True,\n            \"avatar_url\": get_avatar_url(user),\n            \"email\": user.email,\n            \"subscriber_number\": user.subscriber_number,\n        }\n        if UserSubscription.objects.filter(user=user, canceled__isnull=True).exists():\n            data[\"is_subscriber\"] = True\n        if user.is_staff:\n            data[\"is_staff\"] = True\n        if user.is_superuser:\n            data[\"is_superuser\"] = True\n        if user.is_beta_tester:\n            data[\"is_beta_tester\"] = True\n\n        # This is rather temporary field. Once we're off the Wiki and into Yari\n        # this no longer makes sense to keep.\n        data[\"wiki_contributions\"] = user.created_revisions.count()\n    else:\n        data = {}\n\n    # Add waffle data to the dict we're going to be returning.\n    # This is what the waffle.wafflejs() template tag does, but we're\n    # doing it via an API instead of hardcoding the settings into\n    # the HTML page. See also from waffle.views._generate_waffle_js.\n    #\n    # Note that if we upgrade django-waffle, version 15 introduces a\n    # pluggable flag model, and the approved way to get all flag\n    # objects will then become:\n    #    get_waffle_flag_model().get_all()\n    #\n    data[\"waffle\"] = {\n        \"flags\": {f.name: True for f in Flag.get_all() if f.is_active(request)},\n        \"switches\": {s.name: True for s in Switch.get_all() if s.is_active()},\n        \"samples\": {s.name: True for s in Sample.get_all() if s.is_active()},\n    }\n    return JsonResponse(data)\n\n\nclass APIDocumentSerializer(serializers.Serializer):\n    title = serializers.CharField(read_only=True, max_length=255)\n    slug = serializers.CharField(read_only=True, max_length=255)\n    locale = serializers.CharField(read_only=True, max_length=7)\n    excerpt = serializers.ReadOnlyField(source=\"get_excerpt\")\n\n\nclass APILanguageFilterBackend(LanguageFilterBackend):\n    \"\"\"Override of kuma.search.filters:LanguageFilterBackend that is almost\n    exactly the same except the locale comes from custom code rather than\n    via kuma.core.i18n.get_language_from_request because that can't be used\n    in the API.\n\n    Basically, it's the same exact functionality but ...\n    \"\"\"\n\n    def filter_queryset(self, request, queryset, view):\n        locale = request.GET.get(\"locale\") or settings.LANGUAGE_CODE\n        if locale not in settings.ACCEPTED_LOCALES:\n            raise serializers.ValidationError({\"error\": \"Not a valid locale code\"})\n        request.LANGUAGE_CODE = locale\n        return super(APILanguageFilterBackend, self).filter_queryset(\n            request, queryset, view\n        )\n\n\nclass APISearchView(SearchView):\n    serializer_class = APIDocumentSerializer\n    renderer_classes = [JSONRenderer]\n    filter_backends = (\n        SearchQueryBackend,\n        KeywordQueryBackend,\n        TagGroupFilterBackend,\n        APILanguageFilterBackend,\n        HighlightFilterBackend,\n    )\n\n\nsearch = never_cache(APISearchView.as_view())\n\n\n@ratelimit(key=\"user_or_ip\", rate=\"10/d\", block=True)\n@api_view([\"POST\"])\ndef bc_signal(request):\n    if not settings.ENABLE_BCD_SIGNAL:\n        return Response(\"not enabled\", status=status.HTTP_400_BAD_REQUEST)\n\n    serializer = BCSignalSerializer(data=request.data)\n    if serializer.is_valid():\n        serializer.save()\n        return Response(serializer.validated_data, status=status.HTTP_201_CREATED)\n    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)\n\n\n@waffle_flag(\"subscription\")\n@never_cache\n@require_POST\ndef send_subscriptions_feedback(request):\n    \"\"\"\n    Sends feedback to Google Analytics. This is done on the\n    backend to ensure that all feedback is collected, even\n    from users with DNT or where GA is disabled.\n    \"\"\"\n    data = json.loads(request.body)\n    feedback = (data.get(\"feedback\") or \"\").strip()\n\n    if not feedback:\n        return HttpResponseBadRequest(\"no feedback\")\n\n    track_event(\n        CATEGORY_MONTHLY_PAYMENTS, ACTION_SUBSCRIPTION_FEEDBACK, data[\"feedback\"]\n    )\n    return HttpResponse(status=204)\n\n\n@api_view([\"POST\", \"GET\", \"DELETE\"])\n@never_cache\ndef subscriptions(request):\n    if not request.user.is_authenticated or not flag_is_active(request, \"subscription\"):\n        return Response(None, status=status.HTTP_403_FORBIDDEN)\n\n    if request.method == \"POST\":\n        create_stripe_customer_and_subscription_for_user(\n            request.user, request.user.email, request.data[\"stripe_token\"]\n        )\n        return Response(None, status=status.HTTP_201_CREATED)\n    elif request.method == \"DELETE\":\n        cancelled = cancel_stripe_customer_subscriptions(request.user)\n        if cancelled:\n            return Response(None, status=status.HTTP_204_NO_CONTENT)\n        else:\n            return Response(\"nothing to cancel\", status=status.HTTP_410_GONE)\n\n    all_subscriptions = []\n    subscription_info = retrieve_and_synchronize_subscription_info(request.user)\n    if subscription_info:\n        all_subscriptions.append(subscription_info)\n\n    return Response({\"subscriptions\": all_subscriptions})\n\n\n@csrf_exempt\n@require_POST\n@never_cache\ndef stripe_hooks(request):\n    try:\n        payload = json.loads(request.body)\n    except ValueError:\n        return HttpResponseBadRequest(\"Invalid JSON payload\")\n\n    try:\n        event = stripe.Event.construct_from(payload, stripe.api_key)\n    except stripe.error.StripeError:\n        raven_client.captureException()\n        return HttpResponseBadRequest()\n\n    # Generally, for this list of if-statements, see the create_missing_stripe_webhook\n    # function.\n    # The list of events there ought to at least minimally match what we're prepared\n    # to deal with here.\n\n    if event.type == \"invoice.payment_succeeded\":\n        invoice = event.data.object\n        _send_payment_received_email(invoice, request.LANGUAGE_CODE)\n        track_event(\n            CATEGORY_MONTHLY_PAYMENTS,\n            ACTION_SUBSCRIPTION_CREATED,\n            f\"{settings.CONTRIBUTION_AMOUNT_USD:.2f}\",\n        )\n\n    elif event.type == \"customer.subscription.deleted\":\n        obj = event.data.object\n        for user in User.objects.filter(stripe_customer_id=obj.customer):\n            UserSubscription.set_canceled(user, obj.id)\n        track_event(CATEGORY_MONTHLY_PAYMENTS, ACTION_SUBSCRIPTION_CANCELED, \"webhook\")\n\n    else:\n        return HttpResponseBadRequest(\n            f\"We did not expect a Stripe webhook of type {event.type!r}\"\n        )\n\n    return HttpResponse()\n\n\ndef _send_payment_received_email(invoice, locale):\n    user = get_user_model().objects.get(stripe_customer_id=invoice.customer)\n    subscription_info = retrieve_and_synchronize_subscription_info(user)\n    locale = locale or settings.WIKI_DEFAULT_LANGUAGE\n    context = {\n        \"payment_date\": datetime.fromtimestamp(invoice.created),\n        \"next_payment_date\": subscription_info[\"next_payment_at\"],\n        \"invoice_number\": invoice.number,\n        \"cost\": invoice.total / 100,\n        \"credit_card_brand\": subscription_info[\"brand\"],\n        \"manage_subscription_url\": absolutify(reverse(\"payment_management\")),\n        \"faq_url\": absolutify(reverse(\"payments_index\")),\n        \"contact_email\": settings.CONTRIBUTION_SUPPORT_EMAIL,\n    }\n    with translation.override(locale):\n        subject = render_email(\"users/email/payment_received/subject.ltxt\", context)\n        # Email subject *must not* contain newlines\n        subject = \"\".join(subject.splitlines())\n        plain = render_email(\"users/email/payment_received/plain.ltxt\", context)\n\n        send_mail_retrying(\n            subject,\n            plain,\n            settings.DEFAULT_FROM_EMAIL,\n            [user.email],\n            attachment={\n                \"name\": os.path.basename(urlparse(invoice.invoice_pdf).path),\n                \"bytes\": _download_from_url(invoice.invoice_pdf),\n                \"mime\": \"application/pdf\",\n            },\n        )\n\n\ndef _download_from_url(url):\n    pdf_download = requests_retry_session().get(url)\n    pdf_download.raise_for_status()\n    return pdf_download.content\n", "path": "kuma/api/v1/views.py"}]}