diff --git a/ttadmin/users/forms.py b/ttadmin/users/forms.py
new file mode 100644
index 0000000..be11fe3
--- /dev/null
+++ b/ttadmin/users/forms.py
@@ -0,0 +1,98 @@
+from random import shuffle
+import re
+
+from django.core.exceptions import ValidationError
+from django.forms import Form, CharField, EmailField, Textarea, ChoiceField, BooleanField
+import sshpubkeys as ssh
+
+from .models import SSH_TYPE_CHOICES
+
+USERNAME_RE = re.compile(r'[a-z][a-z0-9_]+')
+USERNAME_MIN_LENGTH = 4
+DISPLAY_NAME_RE = re.compile(r"[a-zA-Z0-9_\-']+")
+DISPLAY_MIN_LENGTH = 2
+
+# >_>
+CAPTCHA_CHOICES = [('two', 'zorp borp'),
+ ('three', 'quop bop'),
+ ('four', 'NO, I AM NOT A ROBOT'),
+ ('five', 'crackle zop'),
+ ('six', '*rusty screech*'),
+ ('seven', 'mother, give me legs')]
+shuffle(CAPTCHA_CHOICES)
+CAPTCHA_CHOICES.insert(0, ('one', 'beep boop'),)
+NOT_A_ROBOT = 'four'
+# <_<
+
+def validate_username(username):
+ if len(username) < USERNAME_MIN_LENGTH:
+ raise ValidationError('Username too short.')
+ if not USERNAME_RE.match(username):
+ raise ValidationError('Username must be all lowercase, start with a letter, and only use the _ special charcter')
+
+def validate_displayname(display_name):
+ if len(display_name) < DISPLAY_MIN_LENGTH:
+ raise ValidationError('Display name too short.')
+ if not DISPLAY_NAME_RE.match(display_name):
+ raise ValidationError("Valid characters: a-z, A-Z, 0-9, -, _, and '.")
+
+def validate_pubkey(pubkey):
+ key = ssh.SSHKey(pubkey, strict_mode=False, skip_option_parsing=True)
+ try:
+ key.parse()
+ except ssh.InvalidKeyException as e:
+ raise ValidationError('Could not validate key: {}'.format(e))
+ except NotImplementedError as e:
+ raise ValidationError('Invalid key type')
+ except Exception as e:
+ raise ValidationError('unknown error: {}'.format(e))
+
+def validate_captcha(captcha):
+ if captcha != NOT_A_ROBOT:
+ raise ValidationError('Are you sure you are not a robot?')
+
+
+class TownieForm(Form):
+ username = CharField(
+ validators=(validate_username,),
+ help_text='lowercase and no spaces. underscore ok',
+ label='username')
+ email = EmailField(
+ help_text='only used to message you about your account and nothing else.',
+ label='e-mail')
+ displayname = CharField(
+ validators=(validate_displayname,),
+ help_text='100% optional. pseudonyms welcome.',
+ label='display name',
+ required=False)
+ reasons = CharField(
+ widget=Textarea,
+ required=False,
+ label='what interests you about tilde.town?',
+ help_text='This is a totally optional place for you to tell us what excites you about getting a ~ account. This is mainly just so we can all feel warm fuzzies.')
+ captcha = ChoiceField(
+ choices=CAPTCHA_CHOICES,
+ label='are you a robot?',
+ help_text='pick the response that indicates whether or not you are a robot.',
+ validators=(validate_captcha,)
+ )
+ pubkey = CharField(
+ widget=Textarea,
+ validators=(validate_pubkey,),
+ label='SSH public key',
+ help_text='if this is not a thing you are familiar with, that\'s okay! check out our guide to learn how to get one of these.')
+ pubkey_type = ChoiceField(
+ choices=SSH_TYPE_CHOICES,
+ label='SSH public key type',
+ help_text="unless you know what you're doing you can leave this be.")
+ aup = BooleanField(
+ label='i super agree to our acceptable use policy',
+ help_text='please read our code of conduct and click this box if you agree.')
+
+ def clean(self):
+ result = super().clean()
+ if self.errors:
+ raise ValidationError('oops, looks like there were some problems below.')
+ return result
+
+
diff --git a/ttadmin/users/models.py b/ttadmin/users/models.py
index 9d45224..20d234e 100644
--- a/ttadmin/users/models.py
+++ b/ttadmin/users/models.py
@@ -1,19 +1,24 @@
+import re
+
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.db.models import TextField, BooleanField, CharField
+
SSH_TYPE_CHOICES = (
('ssh-rsa', 'ssh-rsa',),
('ssh-dss', 'ssh-dss',),
)
+
class Townie(User):
"""Both an almost normal Django User as well as an abstraction over a
system user."""
pubkey = TextField(blank=False, null=False)
shell = CharField(max_length=50, default="/bin/bash")
reviewed = BooleanField(default=False)
+ reasons = TextField(blank=True, null=False, default='')
displayname = CharField(max_length=100, blank=False, null=False)
pubkey_type = CharField(max_length=15,
blank=False,
diff --git a/ttadmin/users/urls.py b/ttadmin/users/urls.py
index 9e4f7ba..b11c6d3 100644
--- a/ttadmin/users/urls.py
+++ b/ttadmin/users/urls.py
@@ -1,9 +1,9 @@
from django.conf.urls import url
-from .views import UserSignupView
+from .views import SignupView, ThanksView
app_name = 'users'
urlpatterns = [
- url(r'^signup/?$', UserSignupView.as_view(), name='signup'),
-
+ url(r'^signup/?$', SignupView.as_view(), name='signup'),
+ url(r'^thanks/?$', ThanksView.as_view(), name='thanks'),
]
diff --git a/ttadmin/users/views.py b/ttadmin/users/views.py
index 7869981..52f657a 100644
--- a/ttadmin/users/views.py
+++ b/ttadmin/users/views.py
@@ -1,48 +1,37 @@
+import re
+
+from django.core.exceptions import ValidationError
+from django.forms import Form, CharField, EmailField, Textarea, ChoiceField, BooleanField
from django.http import HttpResponse
+from django.shortcuts import redirect
from django.views.generic import TemplateView
+from django.views.generic.edit import FormView
+from django.urls import reverse
-from .models import Townie, SSH_TYPE_CHOICES
+from .forms import TownieForm
+from .models import Townie
-# TODO validation functions for final request validation and live js validation
-# I refuse to duplicate the logic for validation on the front-end and am going
-# to accept round-trip validation costs with long-term caching.
+class SignupView(FormView):
+ form_class = TownieForm
+ template_name = 'users/signup.html'
+ # TODO reverse
+ success_url = '/thanks'
-class UserSignupView(TemplateView):
- template_name = 'ttadmin/signup.html'
+ def form_valid(self, form):
- def get_context_data(self):
- ctx = super().get_context_data()
- ctx['ssh_type_choices'] = SSH_TYPE_CHOICES
- return ctx
+ #t = Townie(
+ # username=username,
+ # displayname=displayname,
+ # pubkey=pubkey,
+ # email=email,
+ #)
- def post(self, request):
- print(request.POST)
- # TODO validate
- username = request.POST.get('username')
+ #t.set_unusable_password()
+ #t.save()
- displayname = request.POST.get('displayname')
-
- if displayname is None:
- displayname = username
- else:
- # TODO validate
- pass
-
- # TODO validate
- pubkey = request.POST.get('pubkey')
-
- # TODO validate
- email = request.POST.get('email')
-
- t = Townie(
- username=username,
- displayname=displayname,
- pubkey=pubkey,
- email=email,
- )
-
- t.set_unusable_password()
- t.save()
+ return super().form_valid(form)
- return HttpResponse('LOLOLOLOL')
+# TODO add template for this once i've fixed template directories
+class ThanksView(TemplateView):
+ template_name = 'users/thanks.html'