We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add `HTML` validation and remove `jQuery` validation from registration form
<!-- IMPORTANT: Before posting, be sure to redact or remove sensitive data, such as passwords, secret keys, session cookies, etc. -->

<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
Dependency to `epic` [#7694](https://github.com/internetarchive/openlibrary/issues/7694). Sub-task of [#9205](https://github.com/internetarchive/openlibrary/issues/9205).

### Describe the problem that you'd like solved
<!-- A clear and concise description of what you want to happen. -->
While the new real-time validation for formatting errors added in [#2055](https://github.com/internetarchive/openlibrary/issues/2055) shows very helpful errors to the user, it does not actually prevent users from submitting forms with badly formatted inputs.

### Proposal & Constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->
While in most cases, this problem could be solved speedily by adding relevant HTML attributes to the inputs, this particular form is generated via the forms library in `form.py`:
In the HTML:
https://github.com/internetarchive/openlibrary/blob/ca46cbfe87af6e81dabc966baa5b75c827097879/openlibrary/templates/account/create.html#L53-L61
In the Python:
https://github.com/internetarchive/openlibrary/blob/ca46cbfe87af6e81dabc966baa5b75c827097879/openlibrary/plugins/upstream/forms.py#L74-L82):

However, luckily it is fairly straightforward to add any desired HTML attributes to the attribute list above, which translate to the correct attributes in the final rendered HTML.

One other issue here, which we also encountered in [#8871](https://github.com/internetarchive/openlibrary/pull/8871), is that the jQuery `validate` plugin, called via the `validate` class in the form, starts adding extra error messages to the email field on key-up. 

Since the `validate` class has no other effect on the form, as validation is handled by `init_realtime_validation` and `forms.py` validators, I propose removing it.

<!-- Which suggestions or requirements should be considered for how feature needs to appear or be implemented? -->

### Additional context
<!-- Add any other context or screenshots about the feature request here. -->
- Form library: [utils/form.py](https://github.com/internetarchive/openlibrary/blob/master/openlibrary/utils/form.py)
- Registration form set-up: [plugins/upstream/forms.py](https://github.com/internetarchive/openlibrary/blob/master/openlibrary/plugins/upstream/forms.py)
- Registration form HTML: [templates/account/create.html](https://github.com/internetarchive/openlibrary/blob/master/openlibrary/templates/account/create.html)

### Stakeholders
<!-- @ tag stakeholders of this bug -->
@cdrini



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `openlibrary/utils/form.py`
Content:
```
1 """New form library to use instead of web.form.
2 
3 (this should go to web.py)
4 """
5 
6 import web
7 import copy
8 import re
9 
10 from infogami.utils.view import render
11 
12 
13 class AttributeList(dict):
14     """List of attributes of input.
15 
16     >>> a = AttributeList(type='text', name='x', value=20)
17     >>> a
18     <attrs: 'type="text" name="x" value="20"'>
19     """
20 
21     def copy(self):
22         return AttributeList(self)
23 
24     def __str__(self):
25         return " ".join(f'{k}="{web.websafe(v)}"' for k, v in self.items())
26 
27     def __repr__(self):
28         return '<attrs: %s>' % repr(str(self))
29 
30 
31 class Input:
32     def __init__(self, name, description=None, value=None, **kw):
33         self.name = name
34         self.description = description or ""
35         self.value = value
36         self.validators = kw.pop('validators', [])
37 
38         self.help = kw.pop('help', None)
39         self.note = kw.pop('note', None)
40 
41         self.id = kw.pop('id', name)
42         self.__dict__.update(kw)
43 
44         if 'klass' in kw:
45             kw['class'] = kw.pop('klass')
46 
47         self.attrs = AttributeList(kw)
48 
49     def get_type(self):
50         raise NotImplementedError
51 
52     def is_hidden(self):
53         return False
54 
55     def render(self):
56         attrs = self.attrs.copy()
57         attrs['id'] = self.id
58         attrs['type'] = self.get_type()
59         attrs['name'] = self.name
60         attrs['value'] = self.value or ''
61 
62         return '<input ' + str(attrs) + ' />'
63 
64     def validate(self, value):
65         self.value = value
66         for v in self.validators:
67             if not v.valid(value):
68                 self.note = v.msg
69                 return False
70         return True
71 
72 
73 class Textbox(Input):
74     """Textbox input.
75 
76     >>> t = Textbox("name", description='Name', value='joe')
77     >>> t.render()
78     '<input type="text" id="name" value="joe" name="name" />'
79 
80     >>> t = Textbox("name", description='Name', value='joe', id='name', klass='input', size=10)
81     >>> t.render()
82     '<input name="name" value="joe" class="input" type="text" id="name" size="10" />'
83     """
84 
85     def get_type(self):
86         return "text"
87 
88 
89 class Password(Input):
90     """Password input.
91 
92     >>> Password("password", description='Password', value='secret').render()
93     '<input type="password" id="password" value="secret" name="password" />'
94     """
95 
96     def get_type(self):
97         return "password"
98 
99 
100 class Checkbox(Input):
101     """Checkbox input."""
102 
103     @property
104     def checked(self):
105         return self.value is not None
106 
107     def get_type(self):
108         return "checkbox"
109 
110     def render(self):
111         if self.value is not None:
112             self.attrs['checked'] = ''
113 
114         return Input.render(self)
115 
116 
117 class Hidden(Input):
118     """Hidden input."""
119 
120     def is_hidden(self):
121         return True
122 
123     def get_type(self):
124         return "hidden"
125 
126 
127 class Form:
128     def __init__(self, *inputs, **kw):
129         self.inputs = inputs
130         self.validators = kw.pop('validators', [])
131         self.note = None
132 
133     def __call__(self):
134         return copy.deepcopy(self)
135 
136     def __str__(self):
137         return web.safestr(self.render())
138 
139     def __getitem__(self, key):
140         for i in self.inputs:
141             if i.name == key:
142                 return i
143         raise KeyError(key)
144 
145     def __getattr__(self, name):
146         # don't interfere with deepcopy
147         inputs = self.__dict__.get('inputs') or []
148         for x in inputs:
149             if x.name == name:
150                 return x
151         raise AttributeError(name)
152 
153     def render(self):
154         return render.form(self)
155 
156     def validates(self, source):
157         valid = True
158 
159         for i in self.inputs:
160             v = source.get(i.name)
161             valid = i.validate(v) and valid
162 
163         valid = self._validate(source) and valid
164         self.valid = valid
165         return valid
166 
167     fill = validates
168 
169     def _validate(self, value):
170         for v in self.validators:
171             if not v.valid(value):
172                 self.note = v.msg
173                 return False
174         return True
175 
176 
177 class Validator:
178     def __init__(self, msg, test):
179         self.msg = msg
180         self.test = test
181 
182     def __deepcopy__(self, memo):
183         return copy.copy(self)
184 
185     def valid(self, value):
186         try:
187             return self.test(value)
188         except:
189             raise
190             return False
191 
192     def __repr__(self):
193         return "<validator: %r >" % self.msg
194 
195 
196 notnull = Validator("Required", bool)
197 
198 
199 class RegexpValidator(Validator):
200     def __init__(self, rexp, msg):
201         self.rexp = re.compile(rexp)
202         self.msg = msg
203 
204     def valid(self, value):
205         return bool(self.rexp.match(value))
206 
207 
208 if __name__ == "__main__":
209     import doctest
210 
211     doctest.testmod()
212
```

Path: `openlibrary/plugins/upstream/forms.py`
Content:
```
1 import web
2 from infogami.infobase.client import ClientException
3 from infogami.core import forms
4 
5 from openlibrary.i18n import lgettext as _
6 from openlibrary.utils.form import (
7     Form,
8     Textbox,
9     Password,
10     Checkbox,
11     Hidden,
12     Validator,
13     RegexpValidator,
14 )
15 from openlibrary import accounts
16 from openlibrary.accounts import InternetArchiveAccount
17 from . import spamcheck
18 
19 
20 def find_account(username=None, lusername=None, email=None):
21     return accounts.find(username=username, lusername=lusername, email=email)
22 
23 
24 def find_ia_account(email=None):
25     ia_account = InternetArchiveAccount.get(email=email)
26     return ia_account
27 
28 
29 Login = Form(
30     Textbox('username', description=_('Username'), klass='required'),
31     Password('password', description=_('Password'), klass='required'),
32     Hidden('redirect'),
33 )
34 forms.login = Login
35 
36 email_already_used = Validator(
37     _("No user registered with this email address"),
38     lambda email: find_account(email=email) is not None,
39 )
40 email_not_already_used = Validator(
41     _("Email already registered"), lambda email: not find_ia_account(email=email)
42 )
43 email_not_disposable = Validator(
44     _("Disposable email not permitted"),
45     lambda email: not email.lower().endswith('dispostable.com'),
46 )
47 email_domain_not_blocked = Validator(
48     _("Your email provider is not recognized."),
49     lambda email: not spamcheck.is_spam_email(email),
50 )
51 username_validator = Validator(
52     _("Username already used"),
53     lambda username: not find_account(lusername=username.lower()),
54 )
55 
56 vlogin = RegexpValidator(
57     r"^[A-Za-z0-9-_]{3,20}$", _('Must be between 3 and 20 letters and numbers')
58 )
59 vpass = RegexpValidator(r".{3,20}", _('Must be between 3 and 20 characters'))
60 vemail = RegexpValidator(r".*@.*", _("Must be a valid email address"))
61 
62 
63 class EqualToValidator(Validator):
64     def __init__(self, fieldname, message):
65         Validator.__init__(self, message, None)
66         self.fieldname = fieldname
67         self.form = None
68 
69     def valid(self, value):
70         # self.form will be set by RegisterForm
71         return self.form[self.fieldname].value == value
72 
73 
74 class RegisterForm(Form):
75     INPUTS = [
76         Textbox(
77             'email',
78             description=_('Your email address'),
79             klass='required',
80             id='emailAddr',
81             validators=[
82                 vemail,
83                 email_not_already_used,
84                 email_not_disposable,
85                 email_domain_not_blocked,
86             ],
87         ),
88         Textbox(
89             'username',
90             description=_(
91                 "Choose a screen name. Screen names are public and cannot be changed "
92                 "later."
93             ),
94             klass='required',
95             help=_("Letters and numbers only please, and at least 3 characters."),
96             autocapitalize="off",
97             validators=[vlogin, username_validator],
98         ),
99         Password(
100             'password',
101             description=_('Choose a password'),
102             klass='required',
103             validators=[vpass],
104         ),
105         Checkbox(
106             'ia_newsletter',
107             description=_(
108                 'I want to receive news, announcements, and resources from the '
109                 '<a href="https://archive.org/">Internet Archive</a>, the non-profit '
110                 'that runs Open Library.'
111             ),
112         ),
113     ]
114 
115     def __init__(self):
116         Form.__init__(self, *self.INPUTS)
117 
118     def validates(self, source):
119         # Set form in each validator so that validators
120         # like EqualToValidator can work
121         for input in self.inputs:
122             for validator in input.validators:
123                 validator.form = self
124         return Form.validates(self, source)
125 
126 
127 Register = RegisterForm()
128 forms.register = RegisterForm()
129 
130 
131 def verify_password(password):
132     user = accounts.get_current_user()
133     if user is None:
134         return False
135 
136     try:
137         username = user.key.split('/')[-1]
138         web.ctx.site.login(username, password)
139     except ClientException:
140         return False
141 
142     return True
143 
144 
145 validate_password = Validator(_("Invalid password"), verify_password)
146 
147 ForgotPassword = Form(
148     Textbox(
149         'email',
150         description=_("Your email address"),
151         validators=[vemail, email_already_used],
152     )
153 )
154 
155 ResetPassword = Form(
156     Password('password', description=_("Choose a password"), validators=[vpass])
157 )
158
```
--- 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/openlibrary/plugins/upstream/forms.py b/openlibrary/plugins/upstream/forms.py
--- a/openlibrary/plugins/upstream/forms.py
+++ b/openlibrary/plugins/upstream/forms.py
@@ -6,6 +6,7 @@
 from openlibrary.utils.form import (
     Form,
     Textbox,
+    Email,
     Password,
     Checkbox,
     Hidden,
@@ -54,7 +55,7 @@
 )
 
 vlogin = RegexpValidator(
-    r"^[A-Za-z0-9-_]{3,20}$", _('Must be between 3 and 20 letters and numbers')
+    r"^[A-Za-z0-9\-_]{3,20}$", _('Must be between 3 and 20 letters and numbers')
 )
 vpass = RegexpValidator(r".{3,20}", _('Must be between 3 and 20 characters'))
 vemail = RegexpValidator(r".*@.*", _("Must be a valid email address"))
@@ -73,11 +74,12 @@
 
 class RegisterForm(Form):
     INPUTS = [
-        Textbox(
+        Email(
             'email',
             description=_('Your email address'),
             klass='required',
             id='emailAddr',
+            required="true",
             validators=[
                 vemail,
                 email_not_already_used,
@@ -95,12 +97,18 @@
             help=_("Letters and numbers only please, and at least 3 characters."),
             autocapitalize="off",
             validators=[vlogin, username_validator],
+            pattern=vlogin.rexp.pattern,
+            title=vlogin.msg,
+            required="true",
         ),
         Password(
             'password',
             description=_('Choose a password'),
             klass='required',
             validators=[vpass],
+            minlength="3",
+            maxlength="20",
+            required="true",
         ),
         Checkbox(
             'ia_newsletter',
diff --git a/openlibrary/utils/form.py b/openlibrary/utils/form.py
--- a/openlibrary/utils/form.py
+++ b/openlibrary/utils/form.py
@@ -97,6 +97,17 @@
         return "password"
 
 
+class Email(Input):
+    """Email input.
+
+    >>> Email("email", value='joe@archive.org').render()
+    '<input type="email" id="email" value="joe@archive.org" name="email" />'
+    """
+
+    def get_type(self):
+        return "email"
+
+
 class Checkbox(Input):
     """Checkbox input."""

{"golden_diff": "diff --git a/openlibrary/plugins/upstream/forms.py b/openlibrary/plugins/upstream/forms.py\n--- a/openlibrary/plugins/upstream/forms.py\n+++ b/openlibrary/plugins/upstream/forms.py\n@@ -6,6 +6,7 @@\n from openlibrary.utils.form import (\n     Form,\n     Textbox,\n+    Email,\n     Password,\n     Checkbox,\n     Hidden,\n@@ -54,7 +55,7 @@\n )\n \n vlogin = RegexpValidator(\n-    r\"^[A-Za-z0-9-_]{3,20}$\", _('Must be between 3 and 20 letters and numbers')\n+    r\"^[A-Za-z0-9\\-_]{3,20}$\", _('Must be between 3 and 20 letters and numbers')\n )\n vpass = RegexpValidator(r\".{3,20}\", _('Must be between 3 and 20 characters'))\n vemail = RegexpValidator(r\".*@.*\", _(\"Must be a valid email address\"))\n@@ -73,11 +74,12 @@\n \n class RegisterForm(Form):\n     INPUTS = [\n-        Textbox(\n+        Email(\n             'email',\n             description=_('Your email address'),\n             klass='required',\n             id='emailAddr',\n+            required=\"true\",\n             validators=[\n                 vemail,\n                 email_not_already_used,\n@@ -95,12 +97,18 @@\n             help=_(\"Letters and numbers only please, and at least 3 characters.\"),\n             autocapitalize=\"off\",\n             validators=[vlogin, username_validator],\n+            pattern=vlogin.rexp.pattern,\n+            title=vlogin.msg,\n+            required=\"true\",\n         ),\n         Password(\n             'password',\n             description=_('Choose a password'),\n             klass='required',\n             validators=[vpass],\n+            minlength=\"3\",\n+            maxlength=\"20\",\n+            required=\"true\",\n         ),\n         Checkbox(\n             'ia_newsletter',\ndiff --git a/openlibrary/utils/form.py b/openlibrary/utils/form.py\n--- a/openlibrary/utils/form.py\n+++ b/openlibrary/utils/form.py\n@@ -97,6 +97,17 @@\n         return \"password\"\n \n \n+class Email(Input):\n+    \"\"\"Email input.\n+\n+    >>> Email(\"email\", value='joe@archive.org').render()\n+    '<input type=\"email\" id=\"email\" value=\"joe@archive.org\" name=\"email\" />'\n+    \"\"\"\n+\n+    def get_type(self):\n+        return \"email\"\n+\n+\n class Checkbox(Input):\n     \"\"\"Checkbox input.\"\"\"\n", "issue": "Add `HTML` validation and remove `jQuery` validation from registration form\n<!-- IMPORTANT: Before posting, be sure to redact or remove sensitive data, such as passwords, secret keys, session cookies, etc. -->\r\n\r\n<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->\r\nDependency to `epic` [#7694](https://github.com/internetarchive/openlibrary/issues/7694). Sub-task of [#9205](https://github.com/internetarchive/openlibrary/issues/9205).\r\n\r\n### Describe the problem that you'd like solved\r\n<!-- A clear and concise description of what you want to happen. -->\r\nWhile the new real-time validation for formatting errors added in [#2055](https://github.com/internetarchive/openlibrary/issues/2055) shows very helpful errors to the user, it does not actually prevent users from submitting forms with badly formatted inputs.\r\n\r\n### Proposal & Constraints\r\n<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->\r\nWhile in most cases, this problem could be solved speedily by adding relevant HTML attributes to the inputs, this particular form is generated via the forms library in `form.py`:\r\nIn the HTML:\r\nhttps://github.com/internetarchive/openlibrary/blob/ca46cbfe87af6e81dabc966baa5b75c827097879/openlibrary/templates/account/create.html#L53-L61\r\nIn the Python:\r\nhttps://github.com/internetarchive/openlibrary/blob/ca46cbfe87af6e81dabc966baa5b75c827097879/openlibrary/plugins/upstream/forms.py#L74-L82):\r\n\r\nHowever, luckily it is fairly straightforward to add any desired HTML attributes to the attribute list above, which translate to the correct attributes in the final rendered HTML.\r\n\r\nOne other issue here, which we also encountered in [#8871](https://github.com/internetarchive/openlibrary/pull/8871), is that the jQuery `validate` plugin, called via the `validate` class in the form, starts adding extra error messages to the email field on key-up. \r\n\r\nSince the `validate` class has no other effect on the form, as validation is handled by `init_realtime_validation` and `forms.py` validators, I propose removing it.\r\n\r\n<!-- Which suggestions or requirements should be considered for how feature needs to appear or be implemented? -->\r\n\r\n### Additional context\r\n<!-- Add any other context or screenshots about the feature request here. -->\r\n- Form library: [utils/form.py](https://github.com/internetarchive/openlibrary/blob/master/openlibrary/utils/form.py)\r\n- Registration form set-up: [plugins/upstream/forms.py](https://github.com/internetarchive/openlibrary/blob/master/openlibrary/plugins/upstream/forms.py)\r\n- Registration form HTML: [templates/account/create.html](https://github.com/internetarchive/openlibrary/blob/master/openlibrary/templates/account/create.html)\r\n\r\n### Stakeholders\r\n<!-- @ tag stakeholders of this bug -->\r\n@cdrini\r\n\r\n\n", "before_files": [{"content": "\"\"\"New form library to use instead of web.form.\n\n(this should go to web.py)\n\"\"\"\n\nimport web\nimport copy\nimport re\n\nfrom infogami.utils.view import render\n\n\nclass AttributeList(dict):\n    \"\"\"List of attributes of input.\n\n    >>> a = AttributeList(type='text', name='x', value=20)\n    >>> a\n    <attrs: 'type=\"text\" name=\"x\" value=\"20\"'>\n    \"\"\"\n\n    def copy(self):\n        return AttributeList(self)\n\n    def __str__(self):\n        return \" \".join(f'{k}=\"{web.websafe(v)}\"' for k, v in self.items())\n\n    def __repr__(self):\n        return '<attrs: %s>' % repr(str(self))\n\n\nclass Input:\n    def __init__(self, name, description=None, value=None, **kw):\n        self.name = name\n        self.description = description or \"\"\n        self.value = value\n        self.validators = kw.pop('validators', [])\n\n        self.help = kw.pop('help', None)\n        self.note = kw.pop('note', None)\n\n        self.id = kw.pop('id', name)\n        self.__dict__.update(kw)\n\n        if 'klass' in kw:\n            kw['class'] = kw.pop('klass')\n\n        self.attrs = AttributeList(kw)\n\n    def get_type(self):\n        raise NotImplementedError\n\n    def is_hidden(self):\n        return False\n\n    def render(self):\n        attrs = self.attrs.copy()\n        attrs['id'] = self.id\n        attrs['type'] = self.get_type()\n        attrs['name'] = self.name\n        attrs['value'] = self.value or ''\n\n        return '<input ' + str(attrs) + ' />'\n\n    def validate(self, value):\n        self.value = value\n        for v in self.validators:\n            if not v.valid(value):\n                self.note = v.msg\n                return False\n        return True\n\n\nclass Textbox(Input):\n    \"\"\"Textbox input.\n\n    >>> t = Textbox(\"name\", description='Name', value='joe')\n    >>> t.render()\n    '<input type=\"text\" id=\"name\" value=\"joe\" name=\"name\" />'\n\n    >>> t = Textbox(\"name\", description='Name', value='joe', id='name', klass='input', size=10)\n    >>> t.render()\n    '<input name=\"name\" value=\"joe\" class=\"input\" type=\"text\" id=\"name\" size=\"10\" />'\n    \"\"\"\n\n    def get_type(self):\n        return \"text\"\n\n\nclass Password(Input):\n    \"\"\"Password input.\n\n    >>> Password(\"password\", description='Password', value='secret').render()\n    '<input type=\"password\" id=\"password\" value=\"secret\" name=\"password\" />'\n    \"\"\"\n\n    def get_type(self):\n        return \"password\"\n\n\nclass Checkbox(Input):\n    \"\"\"Checkbox input.\"\"\"\n\n    @property\n    def checked(self):\n        return self.value is not None\n\n    def get_type(self):\n        return \"checkbox\"\n\n    def render(self):\n        if self.value is not None:\n            self.attrs['checked'] = ''\n\n        return Input.render(self)\n\n\nclass Hidden(Input):\n    \"\"\"Hidden input.\"\"\"\n\n    def is_hidden(self):\n        return True\n\n    def get_type(self):\n        return \"hidden\"\n\n\nclass Form:\n    def __init__(self, *inputs, **kw):\n        self.inputs = inputs\n        self.validators = kw.pop('validators', [])\n        self.note = None\n\n    def __call__(self):\n        return copy.deepcopy(self)\n\n    def __str__(self):\n        return web.safestr(self.render())\n\n    def __getitem__(self, key):\n        for i in self.inputs:\n            if i.name == key:\n                return i\n        raise KeyError(key)\n\n    def __getattr__(self, name):\n        # don't interfere with deepcopy\n        inputs = self.__dict__.get('inputs') or []\n        for x in inputs:\n            if x.name == name:\n                return x\n        raise AttributeError(name)\n\n    def render(self):\n        return render.form(self)\n\n    def validates(self, source):\n        valid = True\n\n        for i in self.inputs:\n            v = source.get(i.name)\n            valid = i.validate(v) and valid\n\n        valid = self._validate(source) and valid\n        self.valid = valid\n        return valid\n\n    fill = validates\n\n    def _validate(self, value):\n        for v in self.validators:\n            if not v.valid(value):\n                self.note = v.msg\n                return False\n        return True\n\n\nclass Validator:\n    def __init__(self, msg, test):\n        self.msg = msg\n        self.test = test\n\n    def __deepcopy__(self, memo):\n        return copy.copy(self)\n\n    def valid(self, value):\n        try:\n            return self.test(value)\n        except:\n            raise\n            return False\n\n    def __repr__(self):\n        return \"<validator: %r >\" % self.msg\n\n\nnotnull = Validator(\"Required\", bool)\n\n\nclass RegexpValidator(Validator):\n    def __init__(self, rexp, msg):\n        self.rexp = re.compile(rexp)\n        self.msg = msg\n\n    def valid(self, value):\n        return bool(self.rexp.match(value))\n\n\nif __name__ == \"__main__\":\n    import doctest\n\n    doctest.testmod()\n", "path": "openlibrary/utils/form.py"}, {"content": "import web\nfrom infogami.infobase.client import ClientException\nfrom infogami.core import forms\n\nfrom openlibrary.i18n import lgettext as _\nfrom openlibrary.utils.form import (\n    Form,\n    Textbox,\n    Password,\n    Checkbox,\n    Hidden,\n    Validator,\n    RegexpValidator,\n)\nfrom openlibrary import accounts\nfrom openlibrary.accounts import InternetArchiveAccount\nfrom . import spamcheck\n\n\ndef find_account(username=None, lusername=None, email=None):\n    return accounts.find(username=username, lusername=lusername, email=email)\n\n\ndef find_ia_account(email=None):\n    ia_account = InternetArchiveAccount.get(email=email)\n    return ia_account\n\n\nLogin = Form(\n    Textbox('username', description=_('Username'), klass='required'),\n    Password('password', description=_('Password'), klass='required'),\n    Hidden('redirect'),\n)\nforms.login = Login\n\nemail_already_used = Validator(\n    _(\"No user registered with this email address\"),\n    lambda email: find_account(email=email) is not None,\n)\nemail_not_already_used = Validator(\n    _(\"Email already registered\"), lambda email: not find_ia_account(email=email)\n)\nemail_not_disposable = Validator(\n    _(\"Disposable email not permitted\"),\n    lambda email: not email.lower().endswith('dispostable.com'),\n)\nemail_domain_not_blocked = Validator(\n    _(\"Your email provider is not recognized.\"),\n    lambda email: not spamcheck.is_spam_email(email),\n)\nusername_validator = Validator(\n    _(\"Username already used\"),\n    lambda username: not find_account(lusername=username.lower()),\n)\n\nvlogin = RegexpValidator(\n    r\"^[A-Za-z0-9-_]{3,20}$\", _('Must be between 3 and 20 letters and numbers')\n)\nvpass = RegexpValidator(r\".{3,20}\", _('Must be between 3 and 20 characters'))\nvemail = RegexpValidator(r\".*@.*\", _(\"Must be a valid email address\"))\n\n\nclass EqualToValidator(Validator):\n    def __init__(self, fieldname, message):\n        Validator.__init__(self, message, None)\n        self.fieldname = fieldname\n        self.form = None\n\n    def valid(self, value):\n        # self.form will be set by RegisterForm\n        return self.form[self.fieldname].value == value\n\n\nclass RegisterForm(Form):\n    INPUTS = [\n        Textbox(\n            'email',\n            description=_('Your email address'),\n            klass='required',\n            id='emailAddr',\n            validators=[\n                vemail,\n                email_not_already_used,\n                email_not_disposable,\n                email_domain_not_blocked,\n            ],\n        ),\n        Textbox(\n            'username',\n            description=_(\n                \"Choose a screen name. Screen names are public and cannot be changed \"\n                \"later.\"\n            ),\n            klass='required',\n            help=_(\"Letters and numbers only please, and at least 3 characters.\"),\n            autocapitalize=\"off\",\n            validators=[vlogin, username_validator],\n        ),\n        Password(\n            'password',\n            description=_('Choose a password'),\n            klass='required',\n            validators=[vpass],\n        ),\n        Checkbox(\n            'ia_newsletter',\n            description=_(\n                'I want to receive news, announcements, and resources from the '\n                '<a href=\"https://archive.org/\">Internet Archive</a>, the non-profit '\n                'that runs Open Library.'\n            ),\n        ),\n    ]\n\n    def __init__(self):\n        Form.__init__(self, *self.INPUTS)\n\n    def validates(self, source):\n        # Set form in each validator so that validators\n        # like EqualToValidator can work\n        for input in self.inputs:\n            for validator in input.validators:\n                validator.form = self\n        return Form.validates(self, source)\n\n\nRegister = RegisterForm()\nforms.register = RegisterForm()\n\n\ndef verify_password(password):\n    user = accounts.get_current_user()\n    if user is None:\n        return False\n\n    try:\n        username = user.key.split('/')[-1]\n        web.ctx.site.login(username, password)\n    except ClientException:\n        return False\n\n    return True\n\n\nvalidate_password = Validator(_(\"Invalid password\"), verify_password)\n\nForgotPassword = Form(\n    Textbox(\n        'email',\n        description=_(\"Your email address\"),\n        validators=[vemail, email_already_used],\n    )\n)\n\nResetPassword = Form(\n    Password('password', description=_(\"Choose a password\"), validators=[vpass])\n)\n", "path": "openlibrary/plugins/upstream/forms.py"}], "after_files": [{"content": "\"\"\"New form library to use instead of web.form.\n\n(this should go to web.py)\n\"\"\"\n\nimport web\nimport copy\nimport re\n\nfrom infogami.utils.view import render\n\n\nclass AttributeList(dict):\n    \"\"\"List of attributes of input.\n\n    >>> a = AttributeList(type='text', name='x', value=20)\n    >>> a\n    <attrs: 'type=\"text\" name=\"x\" value=\"20\"'>\n    \"\"\"\n\n    def copy(self):\n        return AttributeList(self)\n\n    def __str__(self):\n        return \" \".join(f'{k}=\"{web.websafe(v)}\"' for k, v in self.items())\n\n    def __repr__(self):\n        return '<attrs: %s>' % repr(str(self))\n\n\nclass Input:\n    def __init__(self, name, description=None, value=None, **kw):\n        self.name = name\n        self.description = description or \"\"\n        self.value = value\n        self.validators = kw.pop('validators', [])\n\n        self.help = kw.pop('help', None)\n        self.note = kw.pop('note', None)\n\n        self.id = kw.pop('id', name)\n        self.__dict__.update(kw)\n\n        if 'klass' in kw:\n            kw['class'] = kw.pop('klass')\n\n        self.attrs = AttributeList(kw)\n\n    def get_type(self):\n        raise NotImplementedError\n\n    def is_hidden(self):\n        return False\n\n    def render(self):\n        attrs = self.attrs.copy()\n        attrs['id'] = self.id\n        attrs['type'] = self.get_type()\n        attrs['name'] = self.name\n        attrs['value'] = self.value or ''\n\n        return '<input ' + str(attrs) + ' />'\n\n    def validate(self, value):\n        self.value = value\n        for v in self.validators:\n            if not v.valid(value):\n                self.note = v.msg\n                return False\n        return True\n\n\nclass Textbox(Input):\n    \"\"\"Textbox input.\n\n    >>> t = Textbox(\"name\", description='Name', value='joe')\n    >>> t.render()\n    '<input type=\"text\" id=\"name\" value=\"joe\" name=\"name\" />'\n\n    >>> t = Textbox(\"name\", description='Name', value='joe', id='name', klass='input', size=10)\n    >>> t.render()\n    '<input name=\"name\" value=\"joe\" class=\"input\" type=\"text\" id=\"name\" size=\"10\" />'\n    \"\"\"\n\n    def get_type(self):\n        return \"text\"\n\n\nclass Password(Input):\n    \"\"\"Password input.\n\n    >>> Password(\"password\", description='Password', value='secret').render()\n    '<input type=\"password\" id=\"password\" value=\"secret\" name=\"password\" />'\n    \"\"\"\n\n    def get_type(self):\n        return \"password\"\n\n\nclass Email(Input):\n    \"\"\"Email input.\n\n    >>> Email(\"email\", value='joe@archive.org').render()\n    '<input type=\"email\" id=\"email\" value=\"joe@archive.org\" name=\"email\" />'\n    \"\"\"\n\n    def get_type(self):\n        return \"email\"\n\n\nclass Checkbox(Input):\n    \"\"\"Checkbox input.\"\"\"\n\n    @property\n    def checked(self):\n        return self.value is not None\n\n    def get_type(self):\n        return \"checkbox\"\n\n    def render(self):\n        if self.value is not None:\n            self.attrs['checked'] = ''\n\n        return Input.render(self)\n\n\nclass Hidden(Input):\n    \"\"\"Hidden input.\"\"\"\n\n    def is_hidden(self):\n        return True\n\n    def get_type(self):\n        return \"hidden\"\n\n\nclass Form:\n    def __init__(self, *inputs, **kw):\n        self.inputs = inputs\n        self.validators = kw.pop('validators', [])\n        self.note = None\n\n    def __call__(self):\n        return copy.deepcopy(self)\n\n    def __str__(self):\n        return web.safestr(self.render())\n\n    def __getitem__(self, key):\n        for i in self.inputs:\n            if i.name == key:\n                return i\n        raise KeyError(key)\n\n    def __getattr__(self, name):\n        # don't interfere with deepcopy\n        inputs = self.__dict__.get('inputs') or []\n        for x in inputs:\n            if x.name == name:\n                return x\n        raise AttributeError(name)\n\n    def render(self):\n        return render.form(self)\n\n    def validates(self, source):\n        valid = True\n\n        for i in self.inputs:\n            v = source.get(i.name)\n            valid = i.validate(v) and valid\n\n        valid = self._validate(source) and valid\n        self.valid = valid\n        return valid\n\n    fill = validates\n\n    def _validate(self, value):\n        for v in self.validators:\n            if not v.valid(value):\n                self.note = v.msg\n                return False\n        return True\n\n\nclass Validator:\n    def __init__(self, msg, test):\n        self.msg = msg\n        self.test = test\n\n    def __deepcopy__(self, memo):\n        return copy.copy(self)\n\n    def valid(self, value):\n        try:\n            return self.test(value)\n        except:\n            raise\n            return False\n\n    def __repr__(self):\n        return \"<validator: %r >\" % self.msg\n\n\nnotnull = Validator(\"Required\", bool)\n\n\nclass RegexpValidator(Validator):\n    def __init__(self, rexp, msg):\n        self.rexp = re.compile(rexp)\n        self.msg = msg\n\n    def valid(self, value):\n        return bool(self.rexp.match(value))\n\n\nif __name__ == \"__main__\":\n    import doctest\n\n    doctest.testmod()\n", "path": "openlibrary/utils/form.py"}, {"content": "import web\nfrom infogami.infobase.client import ClientException\nfrom infogami.core import forms\n\nfrom openlibrary.i18n import lgettext as _\nfrom openlibrary.utils.form import (\n    Form,\n    Textbox,\n    Email,\n    Password,\n    Checkbox,\n    Hidden,\n    Validator,\n    RegexpValidator,\n)\nfrom openlibrary import accounts\nfrom openlibrary.accounts import InternetArchiveAccount\nfrom . import spamcheck\n\n\ndef find_account(username=None, lusername=None, email=None):\n    return accounts.find(username=username, lusername=lusername, email=email)\n\n\ndef find_ia_account(email=None):\n    ia_account = InternetArchiveAccount.get(email=email)\n    return ia_account\n\n\nLogin = Form(\n    Textbox('username', description=_('Username'), klass='required'),\n    Password('password', description=_('Password'), klass='required'),\n    Hidden('redirect'),\n)\nforms.login = Login\n\nemail_already_used = Validator(\n    _(\"No user registered with this email address\"),\n    lambda email: find_account(email=email) is not None,\n)\nemail_not_already_used = Validator(\n    _(\"Email already registered\"), lambda email: not find_ia_account(email=email)\n)\nemail_not_disposable = Validator(\n    _(\"Disposable email not permitted\"),\n    lambda email: not email.lower().endswith('dispostable.com'),\n)\nemail_domain_not_blocked = Validator(\n    _(\"Your email provider is not recognized.\"),\n    lambda email: not spamcheck.is_spam_email(email),\n)\nusername_validator = Validator(\n    _(\"Username already used\"),\n    lambda username: not find_account(lusername=username.lower()),\n)\n\nvlogin = RegexpValidator(\n    r\"^[A-Za-z0-9\\-_]{3,20}$\", _('Must be between 3 and 20 letters and numbers')\n)\nvpass = RegexpValidator(r\".{3,20}\", _('Must be between 3 and 20 characters'))\nvemail = RegexpValidator(r\".*@.*\", _(\"Must be a valid email address\"))\n\n\nclass EqualToValidator(Validator):\n    def __init__(self, fieldname, message):\n        Validator.__init__(self, message, None)\n        self.fieldname = fieldname\n        self.form = None\n\n    def valid(self, value):\n        # self.form will be set by RegisterForm\n        return self.form[self.fieldname].value == value\n\n\nclass RegisterForm(Form):\n    INPUTS = [\n        Email(\n            'email',\n            description=_('Your email address'),\n            klass='required',\n            id='emailAddr',\n            required=\"true\",\n            validators=[\n                vemail,\n                email_not_already_used,\n                email_not_disposable,\n                email_domain_not_blocked,\n            ],\n        ),\n        Textbox(\n            'username',\n            description=_(\n                \"Choose a screen name. Screen names are public and cannot be changed \"\n                \"later.\"\n            ),\n            klass='required',\n            help=_(\"Letters and numbers only please, and at least 3 characters.\"),\n            autocapitalize=\"off\",\n            validators=[vlogin, username_validator],\n            pattern=vlogin.rexp.pattern,\n            title=vlogin.msg,\n            required=\"true\",\n        ),\n        Password(\n            'password',\n            description=_('Choose a password'),\n            klass='required',\n            validators=[vpass],\n            minlength=\"3\",\n            maxlength=\"20\",\n            required=\"true\",\n        ),\n        Checkbox(\n            'ia_newsletter',\n            description=_(\n                'I want to receive news, announcements, and resources from the '\n                '<a href=\"https://archive.org/\">Internet Archive</a>, the non-profit '\n                'that runs Open Library.'\n            ),\n        ),\n    ]\n\n    def __init__(self):\n        Form.__init__(self, *self.INPUTS)\n\n    def validates(self, source):\n        # Set form in each validator so that validators\n        # like EqualToValidator can work\n        for input in self.inputs:\n            for validator in input.validators:\n                validator.form = self\n        return Form.validates(self, source)\n\n\nRegister = RegisterForm()\nforms.register = RegisterForm()\n\n\ndef verify_password(password):\n    user = accounts.get_current_user()\n    if user is None:\n        return False\n\n    try:\n        username = user.key.split('/')[-1]\n        web.ctx.site.login(username, password)\n    except ClientException:\n        return False\n\n    return True\n\n\nvalidate_password = Validator(_(\"Invalid password\"), verify_password)\n\nForgotPassword = Form(\n    Textbox(\n        'email',\n        description=_(\"Your email address\"),\n        validators=[vemail, email_already_used],\n    )\n)\n\nResetPassword = Form(\n    Password('password', description=_(\"Choose a password\"), validators=[vpass])\n)\n", "path": "openlibrary/plugins/upstream/forms.py"}]}