We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Our flake8 tests suddenly failed
**Summary**
Builds from `master` currently fail in Python linting. 
It's because the version of [flake8 used in TravisCI](https://github.com/mdn/kuma/blob/59afefbe55ca8456203baa96ba421f1fa3b63ede/.travis.yml#L17-L18) don't match what we have in our pyproject.toml. 
Today a new release of flake8 came out that broke our master. [Example](https://travis-ci.com/github/mdn/kuma/jobs/332498567)


--- 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/search/views.py`
Content:
```
1 from django.shortcuts import render
2 from django.urls import reverse_lazy
3 from django.views.decorators.cache import never_cache
4 from django.views.decorators.http import require_GET
5 from django.views.generic import RedirectView
6 from ratelimit.decorators import ratelimit
7 
8 from kuma.api.v1.views import search as search_api
9 from kuma.core.decorators import shared_cache_control
10 from kuma.core.utils import is_wiki
11 
12 from .search import SearchView
13 
14 # Since the search endpoint accepts user input (via query parameters) and its
15 # response is compressed, use rate limiting to mitigate the BREACH attack
16 # (see http://breachattack.com/). It still needs to allow a user to click
17 # the filter switches (bug 1426968).
18 # Alternate: forbid gzip by setting Content-Encoding: identity
19 @never_cache
20 @require_GET
21 @ratelimit(key="user_or_ip", rate="25/m", block=True)
22 def search(request, *args, **kwargs):
23     """
24     The search view.
25     """
26     if is_wiki(request):
27         return wiki_search(request, *args, **kwargs)
28 
29     # The underlying v1 API supports searching without a 'q' but the web
30     # UI doesn't. For example, the search input field requires a value.
31     # So we match that here too.
32     if not request.GET.get("q", "").strip():
33         status = 400
34         context = {"results": {}}
35     else:
36         results = search_api(request, *args, **kwargs).data
37 
38         # Determine if there were validation errors
39         error = results.get("error") or results.get("q")
40         # If q is returned in the data, there was a validation error for that field,
41         # so return 400 status.
42         status = 200 if results.get("q") is None else 400
43         # If there was an error with the pagination you'll get...
44         if results.get("detail"):
45             error = str(results["detail"])
46             status = 400
47 
48         context = {"results": {"results": None if error else results, "error": error}}
49     return render(request, "search/react.html", context, status=status)
50 
51 
52 wiki_search = SearchView.as_view()
53 
54 
55 class SearchRedirectView(RedirectView):
56     permanent = True
57 
58     def get_redirect_url(self, *args, **kwargs):
59         query_string = self.request.META.get("QUERY_STRING")
60         url = reverse_lazy(
61             "api.v1.search", kwargs={"locale": self.request.LANGUAGE_CODE}
62         )
63         if query_string:
64             url += "?" + query_string
65         return url
66 
67 
68 @shared_cache_control(s_maxage=60 * 60 * 24 * 7)
69 def plugin(request):
70     """Render an OpenSearch Plugin."""
71     return render(
72         request,
73         "search/plugin.html",
74         {"locale": request.LANGUAGE_CODE},
75         content_type="application/opensearchdescription+xml",
76     )
77
```

Path: `kuma/users/models.py`
Content:
```
1 import datetime
2 
3 from django.conf import settings
4 from django.contrib.auth.models import AbstractUser
5 from django.contrib.auth.tokens import default_token_generator
6 from django.core import validators
7 from django.core.cache import cache
8 from django.db import models, transaction
9 from django.db.models import Max
10 from django.dispatch import receiver
11 from django.utils import timezone
12 from django.utils.encoding import force_bytes
13 from django.utils.functional import cached_property
14 from django.utils.http import urlsafe_base64_encode
15 from django.utils.translation import gettext_lazy as _
16 
17 from kuma.core.urlresolvers import reverse
18 
19 from .constants import USERNAME_REGEX
20 
21 
22 class UserBan(models.Model):
23     user = models.ForeignKey(
24         settings.AUTH_USER_MODEL,
25         related_name="bans",
26         verbose_name="Banned user",
27         on_delete=models.CASCADE,
28     )
29     by = models.ForeignKey(
30         settings.AUTH_USER_MODEL,
31         related_name="bans_issued",
32         verbose_name="Banned by",
33         on_delete=models.PROTECT,
34     )
35     reason = models.TextField()
36     date = models.DateField(default=datetime.date.today)
37     is_active = models.BooleanField(default=True, help_text="(Is ban active)")
38 
39     def __str__(self):
40         message = _("%(banned_user)s banned by %(banned_by)s") % {
41             "banned_user": self.user,
42             "banned_by": self.by,
43         }
44         if not self.is_active:
45             message = _("%s (no longer active)") % message
46         return message
47 
48 
49 class User(AbstractUser):
50     """
51     Our custom user class.
52     """
53 
54     timezone = models.CharField(
55         verbose_name=_("Timezone"),
56         max_length=42,
57         blank=True,
58         default=settings.TIME_ZONE,
59         # Note the deliberate omission of the `choices=` here.
60         # That's because there's no good way to list all possible
61         # timezones as a 2-D tuple. The *name* of the timezone rarely
62         # changes but the human-friendly description of it easily does.
63     )
64     locale = models.CharField(
65         max_length=7,
66         default=settings.LANGUAGE_CODE,
67         choices=settings.SORTED_LANGUAGES,
68         verbose_name=_("Language"),
69         blank=True,
70         db_index=True,
71     )
72     homepage = models.URLField(
73         verbose_name=_("Homepage"),
74         max_length=255,
75         blank=True,
76         error_messages={
77             "invalid": _(
78                 "This URL has an invalid format. "
79                 "Valid URLs look like http://example.com/my_page."
80             )
81         },
82     )
83     title = models.CharField(verbose_name=_("Title"), max_length=255, blank=True,)
84     fullname = models.CharField(verbose_name=_("Name"), max_length=255, blank=True,)
85     organization = models.CharField(
86         verbose_name=_("Organization"), max_length=255, blank=True,
87     )
88     location = models.CharField(verbose_name=_("Location"), max_length=255, blank=True,)
89     bio = models.TextField(verbose_name=_("About Me"), blank=True,)
90     irc_nickname = models.CharField(
91         verbose_name=_("IRC nickname"), max_length=255, blank=True,
92     )
93 
94     is_newsletter_subscribed = models.BooleanField(default=False)
95 
96     WEBSITE_VALIDATORS = {
97         "website": validators.RegexValidator(
98             r"^https?://", _("Enter a valid website URL."), "invalid",
99         ),
100         "twitter": validators.RegexValidator(
101             r"^https?://twitter\.com/", _("Enter a valid Twitter URL."), "invalid",
102         ),
103         "github": validators.RegexValidator(
104             r"^https?://github\.com/", _("Enter a valid GitHub URL."), "invalid",
105         ),
106         "stackoverflow": validators.RegexValidator(
107             r"^https?://([a-z]{2}\.)?stackoverflow\.com/users/",
108             _("Enter a valid Stack Overflow URL."),
109             "invalid",
110         ),
111         "linkedin": validators.RegexValidator(
112             r"^https?://((www|\w\w)\.)?linkedin.com/((in/[^/]+/?)|(pub/[^/]+/((\w|\d)+/?){3}))$",
113             _("Enter a valid LinkedIn URL."),
114             "invalid",
115         ),
116         "mozillians": validators.RegexValidator(
117             r"^https?://mozillians\.org/u/",
118             _("Enter a valid Mozillians URL."),
119             "invalid",
120         ),
121         "facebook": validators.RegexValidator(
122             r"^https?://www\.facebook\.com/",
123             _("Enter a valid Facebook URL."),
124             "invalid",
125         ),
126         "discourse": validators.RegexValidator(
127             r"^https://discourse\.mozilla\.org/u/",
128             _("Enter a valid Discourse URL."),
129             "invalid",
130         ),
131     }
132 
133     # a bunch of user URLs
134     website_url = models.TextField(
135         _("Website"), blank=True, validators=[WEBSITE_VALIDATORS["website"]],
136     )
137     mozillians_url = models.TextField(
138         _("Mozillians"), blank=True, validators=[WEBSITE_VALIDATORS["mozillians"]],
139     )
140     github_url = models.TextField(
141         _("GitHub"), blank=True, validators=[WEBSITE_VALIDATORS["github"]],
142     )
143     is_github_url_public = models.BooleanField(_("Public GitHub URL"), default=False,)
144     twitter_url = models.TextField(
145         _("Twitter"), blank=True, validators=[WEBSITE_VALIDATORS["twitter"]],
146     )
147     linkedin_url = models.TextField(
148         _("LinkedIn"), blank=True, validators=[WEBSITE_VALIDATORS["linkedin"]],
149     )
150     facebook_url = models.TextField(
151         _("Facebook"), blank=True, validators=[WEBSITE_VALIDATORS["facebook"]],
152     )
153     stackoverflow_url = models.TextField(
154         _("Stack Overflow"),
155         blank=True,
156         validators=[WEBSITE_VALIDATORS["stackoverflow"]],
157     )
158     discourse_url = models.TextField(
159         _("Discourse"), blank=True, validators=[WEBSITE_VALIDATORS["discourse"]],
160     )
161     stripe_customer_id = models.CharField(max_length=255, blank=True)
162 
163     subscriber_number = models.PositiveIntegerField(blank=True, null=True)
164 
165     class Meta:
166         db_table = "auth_user"
167 
168     @property
169     def has_legacy_username(self):
170         return not USERNAME_REGEX.search(self.username)
171 
172     @cached_property
173     def is_beta_tester(self):
174         return settings.BETA_GROUP_NAME in self.groups.values_list("name", flat=True)
175 
176     @cached_property
177     def active_ban(self):
178         """
179         Returns the first active ban for the user or None.
180         """
181         return self.bans.filter(is_active=True).first()
182 
183     def wiki_revisions(self, count=5):
184         return (
185             self.created_revisions.prefetch_related("document")
186             .defer("content", "summary")
187             .order_by("-id")[:count]
188         )
189 
190     def allows_editing_by(self, user):
191         return user.is_staff or user.is_superuser or user.pk == self.pk
192 
193     def get_recovery_url(self):
194         """Creates a recovery URL for the user."""
195         uidb64 = urlsafe_base64_encode(force_bytes(self.pk))
196         token = default_token_generator.make_token(self)
197         link = reverse("users.recover", kwargs={"token": token, "uidb64": uidb64})
198         return link
199 
200     def set_next_subscriber_number_and_save(self):
201         assert not self.subscriber_number, "already set"
202         lock_key = f"set_next_subscriber_number_and_save"
203         # By locking "globally", we get to be certain that our query to get
204         # the current highest `subscriber_number`, gets done alone.
205         with cache.lock(lock_key):
206             highest_number = User.get_highest_subscriber_number()
207             User.objects.filter(id=self.id).update(subscriber_number=highest_number + 1)
208 
209     @classmethod
210     def get_highest_subscriber_number(cls):
211         return (
212             cls.objects.filter(subscriber_number__isnull=False).aggregate(
213                 number=Max("subscriber_number")
214             )["number"]
215             or 0
216         )
217 
218 
219 class UserSubscription(models.Model):
220     user = models.ForeignKey(User, on_delete=models.CASCADE)
221     stripe_subscription_id = models.CharField(max_length=255, blank=True)
222     canceled = models.DateTimeField(null=True)
223     created = models.DateTimeField(auto_now_add=True)
224     updated = models.DateTimeField(auto_now=True)
225 
226     def __str__(self):
227         return (
228             f"{self.user.username} ({self.stripe_subscription_id})"
229             f"{' CANCELED' if self.canceled else ''}"
230         )
231 
232     @classmethod
233     # The reason we make this (class) method transaction atomic is because this
234     # use of `update_or_create` will actually trigger a `post_save` signal
235     # on the `User` model which will set the `User.subscriber_number` if needed.
236     # So, this way we're pre-emptively making sure these two things are atomically
237     # connected.
238     @transaction.atomic()
239     def set_active(cls, user, stripe_subscription_id):
240         cls.objects.update_or_create(
241             stripe_subscription_id=stripe_subscription_id,
242             user=user,
243             defaults={"canceled": None, "updated": timezone.now()},
244         )
245 
246     @classmethod
247     def set_canceled(cls, user, stripe_subscription_id):
248         cls.objects.update_or_create(
249             stripe_subscription_id=stripe_subscription_id,
250             user=user,
251             defaults={"canceled": timezone.now(), "updated": timezone.now()},
252         )
253 
254 
255 @receiver(models.signals.post_save, sender=UserSubscription)
256 def set_user_subscriber_number(sender, instance, **kwargs):
257     if not instance.canceled and not instance.user.subscriber_number:
258         instance.user.set_next_subscriber_number_and_save()
259
```
--- 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/search/views.py b/kuma/search/views.py
--- a/kuma/search/views.py
+++ b/kuma/search/views.py
@@ -11,6 +11,7 @@
 
 from .search import SearchView
 
+
 # Since the search endpoint accepts user input (via query parameters) and its
 # response is compressed, use rate limiting to mitigate the BREACH attack
 # (see http://breachattack.com/). It still needs to allow a user to click
diff --git a/kuma/users/models.py b/kuma/users/models.py
--- a/kuma/users/models.py
+++ b/kuma/users/models.py
@@ -199,7 +199,7 @@
 
     def set_next_subscriber_number_and_save(self):
         assert not self.subscriber_number, "already set"
-        lock_key = f"set_next_subscriber_number_and_save"
+        lock_key = "set_next_subscriber_number_and_save"
         # By locking "globally", we get to be certain that our query to get
         # the current highest `subscriber_number`, gets done alone.
         with cache.lock(lock_key):

{"golden_diff": "diff --git a/kuma/search/views.py b/kuma/search/views.py\n--- a/kuma/search/views.py\n+++ b/kuma/search/views.py\n@@ -11,6 +11,7 @@\n \n from .search import SearchView\n \n+\n # Since the search endpoint accepts user input (via query parameters) and its\n # response is compressed, use rate limiting to mitigate the BREACH attack\n # (see http://breachattack.com/). It still needs to allow a user to click\ndiff --git a/kuma/users/models.py b/kuma/users/models.py\n--- a/kuma/users/models.py\n+++ b/kuma/users/models.py\n@@ -199,7 +199,7 @@\n \n     def set_next_subscriber_number_and_save(self):\n         assert not self.subscriber_number, \"already set\"\n-        lock_key = f\"set_next_subscriber_number_and_save\"\n+        lock_key = \"set_next_subscriber_number_and_save\"\n         # By locking \"globally\", we get to be certain that our query to get\n         # the current highest `subscriber_number`, gets done alone.\n         with cache.lock(lock_key):\n", "issue": "Our flake8 tests suddenly failed\n**Summary**\r\nBuilds from `master` currently fail in Python linting. \r\nIt's because the version of [flake8 used in TravisCI](https://github.com/mdn/kuma/blob/59afefbe55ca8456203baa96ba421f1fa3b63ede/.travis.yml#L17-L18) don't match what we have in our pyproject.toml. \r\nToday a new release of flake8 came out that broke our master. [Example](https://travis-ci.com/github/mdn/kuma/jobs/332498567)\r\n\n", "before_files": [{"content": "from django.shortcuts import render\nfrom django.urls import reverse_lazy\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.http import require_GET\nfrom django.views.generic import RedirectView\nfrom ratelimit.decorators import ratelimit\n\nfrom kuma.api.v1.views import search as search_api\nfrom kuma.core.decorators import shared_cache_control\nfrom kuma.core.utils import is_wiki\n\nfrom .search import SearchView\n\n# Since the search endpoint accepts user input (via query parameters) and its\n# response is compressed, use rate limiting to mitigate the BREACH attack\n# (see http://breachattack.com/). It still needs to allow a user to click\n# the filter switches (bug 1426968).\n# Alternate: forbid gzip by setting Content-Encoding: identity\n@never_cache\n@require_GET\n@ratelimit(key=\"user_or_ip\", rate=\"25/m\", block=True)\ndef search(request, *args, **kwargs):\n    \"\"\"\n    The search view.\n    \"\"\"\n    if is_wiki(request):\n        return wiki_search(request, *args, **kwargs)\n\n    # The underlying v1 API supports searching without a 'q' but the web\n    # UI doesn't. For example, the search input field requires a value.\n    # So we match that here too.\n    if not request.GET.get(\"q\", \"\").strip():\n        status = 400\n        context = {\"results\": {}}\n    else:\n        results = search_api(request, *args, **kwargs).data\n\n        # Determine if there were validation errors\n        error = results.get(\"error\") or results.get(\"q\")\n        # If q is returned in the data, there was a validation error for that field,\n        # so return 400 status.\n        status = 200 if results.get(\"q\") is None else 400\n        # If there was an error with the pagination you'll get...\n        if results.get(\"detail\"):\n            error = str(results[\"detail\"])\n            status = 400\n\n        context = {\"results\": {\"results\": None if error else results, \"error\": error}}\n    return render(request, \"search/react.html\", context, status=status)\n\n\nwiki_search = SearchView.as_view()\n\n\nclass SearchRedirectView(RedirectView):\n    permanent = True\n\n    def get_redirect_url(self, *args, **kwargs):\n        query_string = self.request.META.get(\"QUERY_STRING\")\n        url = reverse_lazy(\n            \"api.v1.search\", kwargs={\"locale\": self.request.LANGUAGE_CODE}\n        )\n        if query_string:\n            url += \"?\" + query_string\n        return url\n\n\n@shared_cache_control(s_maxage=60 * 60 * 24 * 7)\ndef plugin(request):\n    \"\"\"Render an OpenSearch Plugin.\"\"\"\n    return render(\n        request,\n        \"search/plugin.html\",\n        {\"locale\": request.LANGUAGE_CODE},\n        content_type=\"application/opensearchdescription+xml\",\n    )\n", "path": "kuma/search/views.py"}, {"content": "import datetime\n\nfrom django.conf import settings\nfrom django.contrib.auth.models import AbstractUser\nfrom django.contrib.auth.tokens import default_token_generator\nfrom django.core import validators\nfrom django.core.cache import cache\nfrom django.db import models, transaction\nfrom django.db.models import Max\nfrom django.dispatch import receiver\nfrom django.utils import timezone\nfrom django.utils.encoding import force_bytes\nfrom django.utils.functional import cached_property\nfrom django.utils.http import urlsafe_base64_encode\nfrom django.utils.translation import gettext_lazy as _\n\nfrom kuma.core.urlresolvers import reverse\n\nfrom .constants import USERNAME_REGEX\n\n\nclass UserBan(models.Model):\n    user = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        related_name=\"bans\",\n        verbose_name=\"Banned user\",\n        on_delete=models.CASCADE,\n    )\n    by = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        related_name=\"bans_issued\",\n        verbose_name=\"Banned by\",\n        on_delete=models.PROTECT,\n    )\n    reason = models.TextField()\n    date = models.DateField(default=datetime.date.today)\n    is_active = models.BooleanField(default=True, help_text=\"(Is ban active)\")\n\n    def __str__(self):\n        message = _(\"%(banned_user)s banned by %(banned_by)s\") % {\n            \"banned_user\": self.user,\n            \"banned_by\": self.by,\n        }\n        if not self.is_active:\n            message = _(\"%s (no longer active)\") % message\n        return message\n\n\nclass User(AbstractUser):\n    \"\"\"\n    Our custom user class.\n    \"\"\"\n\n    timezone = models.CharField(\n        verbose_name=_(\"Timezone\"),\n        max_length=42,\n        blank=True,\n        default=settings.TIME_ZONE,\n        # Note the deliberate omission of the `choices=` here.\n        # That's because there's no good way to list all possible\n        # timezones as a 2-D tuple. The *name* of the timezone rarely\n        # changes but the human-friendly description of it easily does.\n    )\n    locale = models.CharField(\n        max_length=7,\n        default=settings.LANGUAGE_CODE,\n        choices=settings.SORTED_LANGUAGES,\n        verbose_name=_(\"Language\"),\n        blank=True,\n        db_index=True,\n    )\n    homepage = models.URLField(\n        verbose_name=_(\"Homepage\"),\n        max_length=255,\n        blank=True,\n        error_messages={\n            \"invalid\": _(\n                \"This URL has an invalid format. \"\n                \"Valid URLs look like http://example.com/my_page.\"\n            )\n        },\n    )\n    title = models.CharField(verbose_name=_(\"Title\"), max_length=255, blank=True,)\n    fullname = models.CharField(verbose_name=_(\"Name\"), max_length=255, blank=True,)\n    organization = models.CharField(\n        verbose_name=_(\"Organization\"), max_length=255, blank=True,\n    )\n    location = models.CharField(verbose_name=_(\"Location\"), max_length=255, blank=True,)\n    bio = models.TextField(verbose_name=_(\"About Me\"), blank=True,)\n    irc_nickname = models.CharField(\n        verbose_name=_(\"IRC nickname\"), max_length=255, blank=True,\n    )\n\n    is_newsletter_subscribed = models.BooleanField(default=False)\n\n    WEBSITE_VALIDATORS = {\n        \"website\": validators.RegexValidator(\n            r\"^https?://\", _(\"Enter a valid website URL.\"), \"invalid\",\n        ),\n        \"twitter\": validators.RegexValidator(\n            r\"^https?://twitter\\.com/\", _(\"Enter a valid Twitter URL.\"), \"invalid\",\n        ),\n        \"github\": validators.RegexValidator(\n            r\"^https?://github\\.com/\", _(\"Enter a valid GitHub URL.\"), \"invalid\",\n        ),\n        \"stackoverflow\": validators.RegexValidator(\n            r\"^https?://([a-z]{2}\\.)?stackoverflow\\.com/users/\",\n            _(\"Enter a valid Stack Overflow URL.\"),\n            \"invalid\",\n        ),\n        \"linkedin\": validators.RegexValidator(\n            r\"^https?://((www|\\w\\w)\\.)?linkedin.com/((in/[^/]+/?)|(pub/[^/]+/((\\w|\\d)+/?){3}))$\",\n            _(\"Enter a valid LinkedIn URL.\"),\n            \"invalid\",\n        ),\n        \"mozillians\": validators.RegexValidator(\n            r\"^https?://mozillians\\.org/u/\",\n            _(\"Enter a valid Mozillians URL.\"),\n            \"invalid\",\n        ),\n        \"facebook\": validators.RegexValidator(\n            r\"^https?://www\\.facebook\\.com/\",\n            _(\"Enter a valid Facebook URL.\"),\n            \"invalid\",\n        ),\n        \"discourse\": validators.RegexValidator(\n            r\"^https://discourse\\.mozilla\\.org/u/\",\n            _(\"Enter a valid Discourse URL.\"),\n            \"invalid\",\n        ),\n    }\n\n    # a bunch of user URLs\n    website_url = models.TextField(\n        _(\"Website\"), blank=True, validators=[WEBSITE_VALIDATORS[\"website\"]],\n    )\n    mozillians_url = models.TextField(\n        _(\"Mozillians\"), blank=True, validators=[WEBSITE_VALIDATORS[\"mozillians\"]],\n    )\n    github_url = models.TextField(\n        _(\"GitHub\"), blank=True, validators=[WEBSITE_VALIDATORS[\"github\"]],\n    )\n    is_github_url_public = models.BooleanField(_(\"Public GitHub URL\"), default=False,)\n    twitter_url = models.TextField(\n        _(\"Twitter\"), blank=True, validators=[WEBSITE_VALIDATORS[\"twitter\"]],\n    )\n    linkedin_url = models.TextField(\n        _(\"LinkedIn\"), blank=True, validators=[WEBSITE_VALIDATORS[\"linkedin\"]],\n    )\n    facebook_url = models.TextField(\n        _(\"Facebook\"), blank=True, validators=[WEBSITE_VALIDATORS[\"facebook\"]],\n    )\n    stackoverflow_url = models.TextField(\n        _(\"Stack Overflow\"),\n        blank=True,\n        validators=[WEBSITE_VALIDATORS[\"stackoverflow\"]],\n    )\n    discourse_url = models.TextField(\n        _(\"Discourse\"), blank=True, validators=[WEBSITE_VALIDATORS[\"discourse\"]],\n    )\n    stripe_customer_id = models.CharField(max_length=255, blank=True)\n\n    subscriber_number = models.PositiveIntegerField(blank=True, null=True)\n\n    class Meta:\n        db_table = \"auth_user\"\n\n    @property\n    def has_legacy_username(self):\n        return not USERNAME_REGEX.search(self.username)\n\n    @cached_property\n    def is_beta_tester(self):\n        return settings.BETA_GROUP_NAME in self.groups.values_list(\"name\", flat=True)\n\n    @cached_property\n    def active_ban(self):\n        \"\"\"\n        Returns the first active ban for the user or None.\n        \"\"\"\n        return self.bans.filter(is_active=True).first()\n\n    def wiki_revisions(self, count=5):\n        return (\n            self.created_revisions.prefetch_related(\"document\")\n            .defer(\"content\", \"summary\")\n            .order_by(\"-id\")[:count]\n        )\n\n    def allows_editing_by(self, user):\n        return user.is_staff or user.is_superuser or user.pk == self.pk\n\n    def get_recovery_url(self):\n        \"\"\"Creates a recovery URL for the user.\"\"\"\n        uidb64 = urlsafe_base64_encode(force_bytes(self.pk))\n        token = default_token_generator.make_token(self)\n        link = reverse(\"users.recover\", kwargs={\"token\": token, \"uidb64\": uidb64})\n        return link\n\n    def set_next_subscriber_number_and_save(self):\n        assert not self.subscriber_number, \"already set\"\n        lock_key = f\"set_next_subscriber_number_and_save\"\n        # By locking \"globally\", we get to be certain that our query to get\n        # the current highest `subscriber_number`, gets done alone.\n        with cache.lock(lock_key):\n            highest_number = User.get_highest_subscriber_number()\n            User.objects.filter(id=self.id).update(subscriber_number=highest_number + 1)\n\n    @classmethod\n    def get_highest_subscriber_number(cls):\n        return (\n            cls.objects.filter(subscriber_number__isnull=False).aggregate(\n                number=Max(\"subscriber_number\")\n            )[\"number\"]\n            or 0\n        )\n\n\nclass UserSubscription(models.Model):\n    user = models.ForeignKey(User, on_delete=models.CASCADE)\n    stripe_subscription_id = models.CharField(max_length=255, blank=True)\n    canceled = models.DateTimeField(null=True)\n    created = models.DateTimeField(auto_now_add=True)\n    updated = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return (\n            f\"{self.user.username} ({self.stripe_subscription_id})\"\n            f\"{' CANCELED' if self.canceled else ''}\"\n        )\n\n    @classmethod\n    # The reason we make this (class) method transaction atomic is because this\n    # use of `update_or_create` will actually trigger a `post_save` signal\n    # on the `User` model which will set the `User.subscriber_number` if needed.\n    # So, this way we're pre-emptively making sure these two things are atomically\n    # connected.\n    @transaction.atomic()\n    def set_active(cls, user, stripe_subscription_id):\n        cls.objects.update_or_create(\n            stripe_subscription_id=stripe_subscription_id,\n            user=user,\n            defaults={\"canceled\": None, \"updated\": timezone.now()},\n        )\n\n    @classmethod\n    def set_canceled(cls, user, stripe_subscription_id):\n        cls.objects.update_or_create(\n            stripe_subscription_id=stripe_subscription_id,\n            user=user,\n            defaults={\"canceled\": timezone.now(), \"updated\": timezone.now()},\n        )\n\n\n@receiver(models.signals.post_save, sender=UserSubscription)\ndef set_user_subscriber_number(sender, instance, **kwargs):\n    if not instance.canceled and not instance.user.subscriber_number:\n        instance.user.set_next_subscriber_number_and_save()\n", "path": "kuma/users/models.py"}], "after_files": [{"content": "from django.shortcuts import render\nfrom django.urls import reverse_lazy\nfrom django.views.decorators.cache import never_cache\nfrom django.views.decorators.http import require_GET\nfrom django.views.generic import RedirectView\nfrom ratelimit.decorators import ratelimit\n\nfrom kuma.api.v1.views import search as search_api\nfrom kuma.core.decorators import shared_cache_control\nfrom kuma.core.utils import is_wiki\n\nfrom .search import SearchView\n\n\n# Since the search endpoint accepts user input (via query parameters) and its\n# response is compressed, use rate limiting to mitigate the BREACH attack\n# (see http://breachattack.com/). It still needs to allow a user to click\n# the filter switches (bug 1426968).\n# Alternate: forbid gzip by setting Content-Encoding: identity\n@never_cache\n@require_GET\n@ratelimit(key=\"user_or_ip\", rate=\"25/m\", block=True)\ndef search(request, *args, **kwargs):\n    \"\"\"\n    The search view.\n    \"\"\"\n    if is_wiki(request):\n        return wiki_search(request, *args, **kwargs)\n\n    # The underlying v1 API supports searching without a 'q' but the web\n    # UI doesn't. For example, the search input field requires a value.\n    # So we match that here too.\n    if not request.GET.get(\"q\", \"\").strip():\n        status = 400\n        context = {\"results\": {}}\n    else:\n        results = search_api(request, *args, **kwargs).data\n\n        # Determine if there were validation errors\n        error = results.get(\"error\") or results.get(\"q\")\n        # If q is returned in the data, there was a validation error for that field,\n        # so return 400 status.\n        status = 200 if results.get(\"q\") is None else 400\n        # If there was an error with the pagination you'll get...\n        if results.get(\"detail\"):\n            error = str(results[\"detail\"])\n            status = 400\n\n        context = {\"results\": {\"results\": None if error else results, \"error\": error}}\n    return render(request, \"search/react.html\", context, status=status)\n\n\nwiki_search = SearchView.as_view()\n\n\nclass SearchRedirectView(RedirectView):\n    permanent = True\n\n    def get_redirect_url(self, *args, **kwargs):\n        query_string = self.request.META.get(\"QUERY_STRING\")\n        url = reverse_lazy(\n            \"api.v1.search\", kwargs={\"locale\": self.request.LANGUAGE_CODE}\n        )\n        if query_string:\n            url += \"?\" + query_string\n        return url\n\n\n@shared_cache_control(s_maxage=60 * 60 * 24 * 7)\ndef plugin(request):\n    \"\"\"Render an OpenSearch Plugin.\"\"\"\n    return render(\n        request,\n        \"search/plugin.html\",\n        {\"locale\": request.LANGUAGE_CODE},\n        content_type=\"application/opensearchdescription+xml\",\n    )\n", "path": "kuma/search/views.py"}, {"content": "import datetime\n\nfrom django.conf import settings\nfrom django.contrib.auth.models import AbstractUser\nfrom django.contrib.auth.tokens import default_token_generator\nfrom django.core import validators\nfrom django.core.cache import cache\nfrom django.db import models, transaction\nfrom django.db.models import Max\nfrom django.dispatch import receiver\nfrom django.utils import timezone\nfrom django.utils.encoding import force_bytes\nfrom django.utils.functional import cached_property\nfrom django.utils.http import urlsafe_base64_encode\nfrom django.utils.translation import gettext_lazy as _\n\nfrom kuma.core.urlresolvers import reverse\n\nfrom .constants import USERNAME_REGEX\n\n\nclass UserBan(models.Model):\n    user = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        related_name=\"bans\",\n        verbose_name=\"Banned user\",\n        on_delete=models.CASCADE,\n    )\n    by = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        related_name=\"bans_issued\",\n        verbose_name=\"Banned by\",\n        on_delete=models.PROTECT,\n    )\n    reason = models.TextField()\n    date = models.DateField(default=datetime.date.today)\n    is_active = models.BooleanField(default=True, help_text=\"(Is ban active)\")\n\n    def __str__(self):\n        message = _(\"%(banned_user)s banned by %(banned_by)s\") % {\n            \"banned_user\": self.user,\n            \"banned_by\": self.by,\n        }\n        if not self.is_active:\n            message = _(\"%s (no longer active)\") % message\n        return message\n\n\nclass User(AbstractUser):\n    \"\"\"\n    Our custom user class.\n    \"\"\"\n\n    timezone = models.CharField(\n        verbose_name=_(\"Timezone\"),\n        max_length=42,\n        blank=True,\n        default=settings.TIME_ZONE,\n        # Note the deliberate omission of the `choices=` here.\n        # That's because there's no good way to list all possible\n        # timezones as a 2-D tuple. The *name* of the timezone rarely\n        # changes but the human-friendly description of it easily does.\n    )\n    locale = models.CharField(\n        max_length=7,\n        default=settings.LANGUAGE_CODE,\n        choices=settings.SORTED_LANGUAGES,\n        verbose_name=_(\"Language\"),\n        blank=True,\n        db_index=True,\n    )\n    homepage = models.URLField(\n        verbose_name=_(\"Homepage\"),\n        max_length=255,\n        blank=True,\n        error_messages={\n            \"invalid\": _(\n                \"This URL has an invalid format. \"\n                \"Valid URLs look like http://example.com/my_page.\"\n            )\n        },\n    )\n    title = models.CharField(verbose_name=_(\"Title\"), max_length=255, blank=True,)\n    fullname = models.CharField(verbose_name=_(\"Name\"), max_length=255, blank=True,)\n    organization = models.CharField(\n        verbose_name=_(\"Organization\"), max_length=255, blank=True,\n    )\n    location = models.CharField(verbose_name=_(\"Location\"), max_length=255, blank=True,)\n    bio = models.TextField(verbose_name=_(\"About Me\"), blank=True,)\n    irc_nickname = models.CharField(\n        verbose_name=_(\"IRC nickname\"), max_length=255, blank=True,\n    )\n\n    is_newsletter_subscribed = models.BooleanField(default=False)\n\n    WEBSITE_VALIDATORS = {\n        \"website\": validators.RegexValidator(\n            r\"^https?://\", _(\"Enter a valid website URL.\"), \"invalid\",\n        ),\n        \"twitter\": validators.RegexValidator(\n            r\"^https?://twitter\\.com/\", _(\"Enter a valid Twitter URL.\"), \"invalid\",\n        ),\n        \"github\": validators.RegexValidator(\n            r\"^https?://github\\.com/\", _(\"Enter a valid GitHub URL.\"), \"invalid\",\n        ),\n        \"stackoverflow\": validators.RegexValidator(\n            r\"^https?://([a-z]{2}\\.)?stackoverflow\\.com/users/\",\n            _(\"Enter a valid Stack Overflow URL.\"),\n            \"invalid\",\n        ),\n        \"linkedin\": validators.RegexValidator(\n            r\"^https?://((www|\\w\\w)\\.)?linkedin.com/((in/[^/]+/?)|(pub/[^/]+/((\\w|\\d)+/?){3}))$\",\n            _(\"Enter a valid LinkedIn URL.\"),\n            \"invalid\",\n        ),\n        \"mozillians\": validators.RegexValidator(\n            r\"^https?://mozillians\\.org/u/\",\n            _(\"Enter a valid Mozillians URL.\"),\n            \"invalid\",\n        ),\n        \"facebook\": validators.RegexValidator(\n            r\"^https?://www\\.facebook\\.com/\",\n            _(\"Enter a valid Facebook URL.\"),\n            \"invalid\",\n        ),\n        \"discourse\": validators.RegexValidator(\n            r\"^https://discourse\\.mozilla\\.org/u/\",\n            _(\"Enter a valid Discourse URL.\"),\n            \"invalid\",\n        ),\n    }\n\n    # a bunch of user URLs\n    website_url = models.TextField(\n        _(\"Website\"), blank=True, validators=[WEBSITE_VALIDATORS[\"website\"]],\n    )\n    mozillians_url = models.TextField(\n        _(\"Mozillians\"), blank=True, validators=[WEBSITE_VALIDATORS[\"mozillians\"]],\n    )\n    github_url = models.TextField(\n        _(\"GitHub\"), blank=True, validators=[WEBSITE_VALIDATORS[\"github\"]],\n    )\n    is_github_url_public = models.BooleanField(_(\"Public GitHub URL\"), default=False,)\n    twitter_url = models.TextField(\n        _(\"Twitter\"), blank=True, validators=[WEBSITE_VALIDATORS[\"twitter\"]],\n    )\n    linkedin_url = models.TextField(\n        _(\"LinkedIn\"), blank=True, validators=[WEBSITE_VALIDATORS[\"linkedin\"]],\n    )\n    facebook_url = models.TextField(\n        _(\"Facebook\"), blank=True, validators=[WEBSITE_VALIDATORS[\"facebook\"]],\n    )\n    stackoverflow_url = models.TextField(\n        _(\"Stack Overflow\"),\n        blank=True,\n        validators=[WEBSITE_VALIDATORS[\"stackoverflow\"]],\n    )\n    discourse_url = models.TextField(\n        _(\"Discourse\"), blank=True, validators=[WEBSITE_VALIDATORS[\"discourse\"]],\n    )\n    stripe_customer_id = models.CharField(max_length=255, blank=True)\n\n    subscriber_number = models.PositiveIntegerField(blank=True, null=True)\n\n    class Meta:\n        db_table = \"auth_user\"\n\n    @property\n    def has_legacy_username(self):\n        return not USERNAME_REGEX.search(self.username)\n\n    @cached_property\n    def is_beta_tester(self):\n        return settings.BETA_GROUP_NAME in self.groups.values_list(\"name\", flat=True)\n\n    @cached_property\n    def active_ban(self):\n        \"\"\"\n        Returns the first active ban for the user or None.\n        \"\"\"\n        return self.bans.filter(is_active=True).first()\n\n    def wiki_revisions(self, count=5):\n        return (\n            self.created_revisions.prefetch_related(\"document\")\n            .defer(\"content\", \"summary\")\n            .order_by(\"-id\")[:count]\n        )\n\n    def allows_editing_by(self, user):\n        return user.is_staff or user.is_superuser or user.pk == self.pk\n\n    def get_recovery_url(self):\n        \"\"\"Creates a recovery URL for the user.\"\"\"\n        uidb64 = urlsafe_base64_encode(force_bytes(self.pk))\n        token = default_token_generator.make_token(self)\n        link = reverse(\"users.recover\", kwargs={\"token\": token, \"uidb64\": uidb64})\n        return link\n\n    def set_next_subscriber_number_and_save(self):\n        assert not self.subscriber_number, \"already set\"\n        lock_key = \"set_next_subscriber_number_and_save\"\n        # By locking \"globally\", we get to be certain that our query to get\n        # the current highest `subscriber_number`, gets done alone.\n        with cache.lock(lock_key):\n            highest_number = User.get_highest_subscriber_number()\n            User.objects.filter(id=self.id).update(subscriber_number=highest_number + 1)\n\n    @classmethod\n    def get_highest_subscriber_number(cls):\n        return (\n            cls.objects.filter(subscriber_number__isnull=False).aggregate(\n                number=Max(\"subscriber_number\")\n            )[\"number\"]\n            or 0\n        )\n\n\nclass UserSubscription(models.Model):\n    user = models.ForeignKey(User, on_delete=models.CASCADE)\n    stripe_subscription_id = models.CharField(max_length=255, blank=True)\n    canceled = models.DateTimeField(null=True)\n    created = models.DateTimeField(auto_now_add=True)\n    updated = models.DateTimeField(auto_now=True)\n\n    def __str__(self):\n        return (\n            f\"{self.user.username} ({self.stripe_subscription_id})\"\n            f\"{' CANCELED' if self.canceled else ''}\"\n        )\n\n    @classmethod\n    # The reason we make this (class) method transaction atomic is because this\n    # use of `update_or_create` will actually trigger a `post_save` signal\n    # on the `User` model which will set the `User.subscriber_number` if needed.\n    # So, this way we're pre-emptively making sure these two things are atomically\n    # connected.\n    @transaction.atomic()\n    def set_active(cls, user, stripe_subscription_id):\n        cls.objects.update_or_create(\n            stripe_subscription_id=stripe_subscription_id,\n            user=user,\n            defaults={\"canceled\": None, \"updated\": timezone.now()},\n        )\n\n    @classmethod\n    def set_canceled(cls, user, stripe_subscription_id):\n        cls.objects.update_or_create(\n            stripe_subscription_id=stripe_subscription_id,\n            user=user,\n            defaults={\"canceled\": timezone.now(), \"updated\": timezone.now()},\n        )\n\n\n@receiver(models.signals.post_save, sender=UserSubscription)\ndef set_user_subscriber_number(sender, instance, **kwargs):\n    if not instance.canceled and not instance.user.subscriber_number:\n        instance.user.set_next_subscriber_number_and_save()\n", "path": "kuma/users/models.py"}]}