diff --git a/portal/forms.py b/portal/forms.py index 15d9adb..2e945d3 100644 --- a/portal/forms.py +++ b/portal/forms.py @@ -1,5 +1,6 @@ from allauth.account.forms import SignupForm from django import forms +from django.utils.safestring import mark_safe class CustomSignupForm(SignupForm): @@ -10,6 +11,7 @@ class CustomSignupForm(SignupForm): attrs={'placeholder': 'First Name'} ) ) + last_name = forms.CharField( max_length=200, label='Last Name', @@ -17,10 +19,31 @@ class CustomSignupForm(SignupForm): attrs={'placeholder': 'Last Name'} ) ) + + tos_agreement = forms.BooleanField( + required=True, + label=mark_safe('I agree to the Terms of Service'), + widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) + ) + + coc_agreement = forms.BooleanField( + required=True, + label=mark_safe('I agree to the Code of Conduct'), + widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}) + ) + + field_order = ['email', 'username', 'first_name', 'last_name', 'password1', 'password2', 'tos_agreement', 'coc_agreement'] def save(self, request): user = super().save(request) user.first_name = self.cleaned_data['first_name'] user.last_name = self.cleaned_data['last_name'] user.save() + + from portal_account.models import PortalProfile + profile, _ = PortalProfile.objects.get_or_create(user=user) + profile.coc_agreement = self.cleaned_data.get('coc_agreement') + profile.tos_agreement = self.cleaned_data.get('tos_agreement') + profile.save() + return user \ No newline at end of file diff --git a/portal_account/admin.py b/portal_account/admin.py index 49aedb3..8655f9f 100644 --- a/portal_account/admin.py +++ b/portal_account/admin.py @@ -9,9 +9,11 @@ class PortalProfileAdmin(admin.ModelAdmin): "user__last_name", "pronouns", "coc_agreement", + "tos_agreement", ) search_fields = ("user__email", "user__first_name", "user__last_name") - list_filter = ("pronouns", "coc_agreement") + list_filter = ("pronouns", "coc_agreement", "tos_agreement") + readonly_fields = ("coc_agreement", "tos_agreement") admin.site.register(PortalProfile, PortalProfileAdmin) diff --git a/portal_account/forms.py b/portal_account/forms.py index 182d1fc..5892264 100644 --- a/portal_account/forms.py +++ b/portal_account/forms.py @@ -12,7 +12,7 @@ class PortalProfileForm(ModelForm): class Meta: model = PortalProfile - fields = ["pronouns", "coc_agreement"] + fields = ["pronouns", "coc_agreement", "tos_agreement"] def clean(self): cleaned_data = super().clean() diff --git a/portal_account/migrations/0002_portalprofile_tos_agreement.py b/portal_account/migrations/0002_portalprofile_tos_agreement.py new file mode 100644 index 0000000..6b5fad9 --- /dev/null +++ b/portal_account/migrations/0002_portalprofile_tos_agreement.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.7 on 2025-04-01 00:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('portal_account', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='portalprofile', + name='tos_agreement', + field=models.BooleanField(default=False), + ), + ] diff --git a/portal_account/models.py b/portal_account/models.py index 434c34f..5abbc2e 100644 --- a/portal_account/models.py +++ b/portal_account/models.py @@ -11,6 +11,7 @@ class PortalProfile(BaseModel): user = models.OneToOneField(User, on_delete=models.CASCADE) pronouns = models.CharField(max_length=100, blank=True, null=True) coc_agreement = models.BooleanField(default=False) + tos_agreement = models.BooleanField(default=False) def get_absolute_url(self): return reverse("portal_account:portal_profile_edit", kwargs={"pk": self.pk}) diff --git a/portal_account/tests.py b/portal_account/tests.py index 7ce503c..0970712 100644 --- a/portal_account/tests.py +++ b/portal_account/tests.py @@ -1,3 +1,73 @@ from django.test import TestCase +from django.urls import reverse +from django.contrib.auth.models import User +from portal_account.models import PortalProfile -# Create your tests here. +class SignupTests(TestCase): + def test_signup_requires_tos_agreement(self): + """Test that signup requires Terms of Service agreement.""" + signup_url = reverse('account_signup') + + # Data without tos_agreement + data = { + 'username': 'testuser', + 'email': 'test@example.com', + 'password1': 'testpassword123', + 'password2': 'testpassword123', + 'coc_agreement': True, + # tos_agreement is missing + } + + response = self.client.post(signup_url, data) + + # Check that the form is invalid + self.assertEqual(response.status_code, 200) # Form redisplayed with errors + self.assertFalse(User.objects.filter(username='testuser').exists()) + + def test_signup_requires_coc_agreement(self): + """Test that signup requires Code of Conduct agreement.""" + signup_url = reverse('account_signup') + + # Data without coc_agreement + data = { + 'username': 'testuser', + 'email': 'test@example.com', + 'password1': 'testpassword123', + 'password2': 'testpassword123', + 'tos_agreement': True, + # coc_agreement is missing + } + + response = self.client.post(signup_url, data) + + # Check that the form is invalid + self.assertEqual(response.status_code, 200) # Form redisplayed with errors + self.assertFalse(User.objects.filter(username='testuser').exists()) + + def test_successful_signup_with_agreements(self): + """Test that signup succeeds when both agreements are checked.""" + signup_url = reverse('account_signup') + + # Complete data with both agreements + data = { + 'username': 'testuser', + 'email': 'test@example.com', + 'password1': 'testpassword123', + 'password2': 'testpassword123', + "first_name": "Test", + "last_name": "User", + 'tos_agreement': True, + 'coc_agreement': True, + } + + response = self.client.post(signup_url, data) + + # Check that the user was created and profile has agreement values + self.assertEqual(response.status_code, 302) # Redirect after successful signup + self.assertTrue(User.objects.filter(username='testuser').exists()) + + # Check that the agreement fields were saved to the profile + user = User.objects.get(username='testuser') + profile = PortalProfile.objects.get(user=user) + self.assertTrue(profile.tos_agreement) + self.assertTrue(profile.coc_agreement)