Skip to content

Commit ed56d51

Browse files
enhanced the onboarding form (#100)
1 parent faca5dc commit ed56d51

File tree

7 files changed

+257
-40
lines changed

7 files changed

+257
-40
lines changed

Algolyzer/home/forms.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,69 @@
33
from .models import UserProfile
44

55

6-
class UserProfileDOBForm(forms.ModelForm):
6+
class OnboardingForm(forms.ModelForm):
77
dob = forms.DateField(
8-
widget=forms.DateInput(
9-
attrs={"type": "date", "class": "form-control"}
10-
), # HTML5 date picker
8+
widget=forms.DateInput(attrs={"type": "date", "class": "form-control"}),
119
label="Date of Birth",
1210
required=True,
1311
)
1412

13+
full_name = forms.CharField(
14+
widget=forms.TextInput(attrs={"class": "form-control"}),
15+
label="Full Name",
16+
max_length=100,
17+
required=True,
18+
)
19+
20+
email = forms.EmailField(
21+
widget=forms.EmailInput(attrs={"class": "form-control"}),
22+
label="Email Address",
23+
required=True,
24+
)
25+
26+
phone_number = forms.CharField(
27+
widget=forms.TextInput(
28+
attrs={"class": "form-control", "placeholder": "+1234567890"}
29+
),
30+
label="Phone Number",
31+
max_length=15,
32+
required=True,
33+
)
34+
35+
gender = forms.ChoiceField(
36+
choices=[("M", "Male"), ("F", "Female"), ("O", "Other")],
37+
widget=forms.Select(attrs={"class": "form-control"}),
38+
label="Gender",
39+
required=True,
40+
)
41+
42+
address = forms.CharField(
43+
widget=forms.Textarea(attrs={"class": "form-control", "rows": 3}),
44+
label="Address",
45+
required=True,
46+
)
47+
48+
course = forms.CharField(
49+
widget=forms.TextInput(attrs={"class": "form-control"}),
50+
label="Course Enrolled",
51+
max_length=100,
52+
required=True,
53+
)
54+
55+
enrollment_date = forms.DateField(
56+
widget=forms.DateInput(attrs={"type": "date", "class": "form-control"}),
57+
label="Enrollment Date",
58+
)
59+
1560
class Meta:
1661
model = UserProfile
17-
fields = ["dob"]
62+
fields = [
63+
"full_name",
64+
"dob",
65+
"email",
66+
"phone_number",
67+
"gender",
68+
"address",
69+
"course",
70+
"enrollment_date",
71+
]
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Generated by Django 5.1.4 on 2025-02-11 04:13
2+
3+
import django.utils.timezone
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
dependencies = [
9+
("home", "0001_initial"),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name="userprofile",
15+
name="address",
16+
field=models.TextField(blank=True, null=True),
17+
),
18+
migrations.AddField(
19+
model_name="userprofile",
20+
name="course",
21+
field=models.CharField(default="Undecided", max_length=100),
22+
),
23+
migrations.AddField(
24+
model_name="userprofile",
25+
name="enrollment_date",
26+
field=models.DateField(default=django.utils.timezone.now),
27+
),
28+
migrations.AddField(
29+
model_name="userprofile",
30+
name="full_name",
31+
field=models.CharField(default="Unknown", max_length=100),
32+
preserve_default=False,
33+
),
34+
migrations.AddField(
35+
model_name="userprofile",
36+
name="gender",
37+
field=models.CharField(
38+
blank=True,
39+
choices=[("M", "Male"), ("F", "Female"), ("O", "Other")],
40+
max_length=1,
41+
null=True,
42+
),
43+
),
44+
migrations.AddField(
45+
model_name="userprofile",
46+
name="phone_number",
47+
field=models.CharField(blank=True, max_length=15, null=True),
48+
),
49+
]

Algolyzer/home/models.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,56 @@
11
from django.contrib.auth import get_user_model
22
from django.db import models
3+
from django.utils import timezone
34

45

56
class UserProfile(models.Model):
67
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
8+
9+
# Personal Information
10+
full_name = models.CharField(max_length=100)
711
dob = models.DateField()
8-
# fields for ranking system
12+
phone_number = models.CharField(max_length=15, blank=True, null=True)
13+
gender = models.CharField(
14+
max_length=1,
15+
choices=[("M", "Male"), ("F", "Female"), ("O", "Other")],
16+
blank=True,
17+
null=True,
18+
)
19+
address = models.TextField(blank=True, null=True)
20+
21+
# Education & Enrollment
22+
course = models.CharField(max_length=100, default="Undecided")
23+
enrollment_date = models.DateField(default=timezone.now)
24+
25+
# Ranking System
926
xp = models.PositiveIntegerField(default=0)
1027
level = models.PositiveIntegerField(default=1)
1128

1229
def __str__(self):
1330
return self.user.username
1431

1532
def add_xp(self, amount):
33+
"""Add XP and check for level up"""
1634
self.xp += amount
1735
self.check_level_up()
36+
self.save()
1837

1938
def check_level_up(self):
20-
# Example level thresholds (customize as needed)
21-
level_thresholds = [0, 100, 300, 600, 1000] # XP required for each level
39+
"""Update level based on XP thresholds"""
40+
level_thresholds = [0, 100, 300, 600, 1000, 1500, 2100, 2800, 3600, 4500]
41+
# If user has more exp than our thresholds itself then dynamically create new thresholds
42+
while len(level_thresholds) <= self.level:
43+
level_thresholds.append(
44+
level_thresholds[-1] + (self.level * 500)
45+
) # Custom scaling
46+
2247
for i, threshold in enumerate(level_thresholds):
2348
if self.xp >= threshold:
2449
self.level = i + 1
25-
self.save()
50+
51+
def get_next_level_xp(self):
52+
"""Returns XP required for the next level"""
53+
level_thresholds = [0, 100, 300, 600, 1000, 1500, 2100, 2800, 3600, 4500]
54+
if self.level < len(level_thresholds):
55+
return level_thresholds[self.level] - self.xp
56+
return None # If max level is reached
Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,103 @@
11
{% extends "base.html" %}
22

3-
{% block content%}
4-
<h1>Complete your profile</h1>
5-
<form method="post">
3+
{% block content %}
4+
<div class="container mx-auto">
5+
6+
<h1 class="text-5xl font-bold text-center my-6">Complete Your Profile</h1>
7+
8+
<form method="post" class="space-y-4 border-solid border-primary rounded-xl p-8">
69
{% csrf_token %}
7-
{{ form.as_p }}
8-
<button type="submit">Submit</button>
10+
11+
<!-- Full Name -->
12+
<div class="form-control">
13+
<label class="label font-semibold">Full Name</label>
14+
<input type="text" name="full_name" value="{{ form.full_name.value|default_if_none:'' }}"
15+
class="input input-bordered input-primary w-full" placeholder="Enter your full name" required>
16+
{% if form.full_name.errors %}
17+
<p class="text-error text-sm">{{ form.full_name.errors.0 }}</p>
18+
{% endif %}
19+
</div>
20+
21+
<!-- Date of Birth -->
22+
<div class="form-control">
23+
<label class="label font-semibold">Date of Birth</label>
24+
<input type="date" name="dob" value="{{ form.dob.value|default_if_none:'' }}"
25+
class="input input-bordered input-primary w-full" required>
26+
{% if form.dob.errors %}
27+
<p class="text-error text-sm">{{ form.dob.errors.0 }}</p>
28+
{% endif %}
29+
</div>
30+
31+
<!-- Email Address -->
32+
<div class="form-control">
33+
<label class="label font-semibold">Email Address</label>
34+
<input type="email" name="email" value="{{ form.email.value|default_if_none:'' }}"
35+
class="input input-bordered input-primary w-full" placeholder="[email protected]" required>
36+
{% if form.email.errors %}
37+
<p class="text-error text-sm">{{ form.email.errors.0 }}</p>
38+
{% endif %}
39+
</div>
40+
41+
<!-- Phone Number -->
42+
<div class="form-control">
43+
<label class="label font-semibold">Phone Number</label>
44+
<input type="text" name="phone_number" value="{{ form.phone_number.value|default_if_none:'' }}"
45+
class="input input-bordered input-primary w-full" placeholder="+1234567890">
46+
{% if form.phone_number.errors %}
47+
<p class="text-error text-sm">{{ form.phone_number.errors.0 }}</p>
48+
{% endif %}
49+
</div>
50+
51+
<!-- Gender -->
52+
<div class="form-control">
53+
<label class="label font-semibold">Gender</label>
54+
<select name="gender" class="select select-bordered select-primary w-full">
55+
<option value="" disabled selected>Choose your gender</option>
56+
<option value="M" {% if form.gender.value == "M" %}selected{% endif %}>Male</option>
57+
<option value="F" {% if form.gender.value == "F" %}selected{% endif %}>Female</option>
58+
<option value="O" {% if form.gender.value == "O" %}selected{% endif %}>Other</option>
59+
</select>
60+
{% if form.gender.errors %}
61+
<p class="text-error text-sm">{{ form.gender.errors.0 }}</p>
62+
{% endif %}
63+
</div>
64+
65+
<!-- Address -->
66+
<div class="form-control">
67+
<label class="label font-semibold">Address</label>
68+
<textarea name="address" class="textarea textarea-bordered textarea-primary w-full" rows="3"
69+
placeholder="Enter your address">{{ form.address.value|default_if_none:'' }}</textarea>
70+
{% if form.address.errors %}
71+
<p class="text-error text-sm">{{ form.address.errors.0 }}</p>
72+
{% endif %}
73+
</div>
74+
75+
<!-- Course -->
76+
<div class="form-control">
77+
<label class="label font-semibold">Course Enrolled</label>
78+
<input type="text" name="course" value="{{ form.course.value|default_if_none:'' }}"
79+
class="input input-bordered input-primary w-full" placeholder="Course Name" required>
80+
{% if form.course.errors %}
81+
<p class="text-error text-sm">{{ form.course.errors.0 }}</p>
82+
{% endif %}
83+
</div>
84+
85+
<!-- Enrollment Date -->
86+
<div class="form-control">
87+
<label class="label font-semibold">Enrollment Date</label>
88+
<input type="date" name="enrollment_date" value="{{ form.enrollment_date.value|default_if_none:'' }}"
89+
class="input input-bordered input-primary w-full" required>
90+
{% if form.enrollment_date.errors %}
91+
<p class="text-error text-sm">{{ form.enrollment_date.errors.0 }}</p>
92+
{% endif %}
93+
</div>
94+
95+
<!-- Submit Button -->
96+
<div class="flex justify-between items-center mt-6">
97+
<a href="{% url 'dashboard' %}" class="btn btn-outline">Skip</a>
98+
<button type="submit" class="btn btn-primary">Submit</button>
99+
</div>
9100
</form>
10-
<a href="{% url 'dashboard' %}">Skip</a>
101+
102+
</div>
11103
{% endblock %}

Algolyzer/home/tests.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import datetime
2-
31
from allauth.account.models import EmailAddress
42
from django.contrib.auth import get_user_model
53
from django.test import TestCase
@@ -147,24 +145,6 @@ def test_onboarding_form_is_displayed_if_user_profile_does_not_exist(self):
147145
# Check if the response contains the onboarding form
148146
self.assertContains(response, '<form method="post"')
149147

150-
def test_create_user_profile_on_valid_post(self):
151-
# Login with the test user
152-
self.client.login(email=self.email, password=self.password)
153-
154-
# Send a POST request to create a profile with valid data
155-
data = {"dob": "2000-01-01"} # Example date of birth
156-
response = self.client.post(self.url, data)
157-
158-
# Check if the UserProfile was created
159-
self.assertTrue(UserProfile.objects.filter(user=self.user).exists())
160-
161-
# Check if the response redirects to the dashboard
162-
self.assertRedirects(response, reverse("dashboard"))
163-
164-
# Check if the UserProfile data was saved correctly
165-
user_profile = UserProfile.objects.get(user=self.user)
166-
self.assertEqual(user_profile.dob, datetime.date(2000, 1, 1))
167-
168148
def test_no_profile_creation_on_invalid_post(self):
169149
# Login with the test user
170150
self.client.login(email=self.email, password=self.password)

Algolyzer/home/views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.contrib.auth.decorators import login_required
22
from django.shortcuts import redirect, render
33

4-
from .forms import UserProfileDOBForm
4+
from .forms import OnboardingForm
55
from .models import UserProfile
66

77

@@ -25,15 +25,15 @@ def onboarding(request):
2525

2626
if request.method == "POST":
2727
# If the profile doesn't exist, no instance is passed to the form
28-
form = UserProfileDOBForm(request.POST)
28+
form = OnboardingForm(request.POST)
2929

3030
if form.is_valid():
3131
# Create the UserProfile only after form validation
3232
UserProfile.objects.create(user=request.user, dob=form.cleaned_data["dob"])
3333
return redirect("dashboard")
3434
else:
3535
# On GET request, just create an empty form (no instance)
36-
form = UserProfileDOBForm()
36+
form = OnboardingForm()
3737

3838
return render(request, "home/onboarding.html", {"form": form})
3939

CONTRIBUTING.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,24 @@ The project is built using following versions of software:
7474
# Create a local administrator (make sure you specify values in .env)
7575
python manage.py create_superuser
7676
77-
#seed database
77+
# seed database
7878
python manage.py loaddata data/*
79+
80+
# download aiml models
81+
python manage.py download_models
82+
83+
# start redis as a docker container
84+
docker run --name redis -p 6379:6379 -d redis
85+
86+
# start celery
87+
celery -A Algolyzer worker --loglevel=info
7988
8089
# Now open a NEW TERMINAL and start tailwind in Algolyzer dir
8190
npm run tw_watch
8291
83-
# Goto PREVIOUS TERMINAL and Run Django server.
92+
# Open Third terminal and Run Django server.
93+
source myenv/bin/activate
94+
cd Algolyzer
8495
python manage.py runserver 8000
8596
```
8697

0 commit comments

Comments
 (0)