Skip to content

Commit 4e16910

Browse files
Add profile settings and clean up UI actions
1 parent 72510fd commit 4e16910

63 files changed

Lines changed: 3964 additions & 304 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

accounts/forms.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from django import forms
2+
3+
from accounts.models import CustomUser
4+
5+
6+
class AccountSettingsForm(forms.ModelForm):
7+
class Meta:
8+
model = CustomUser
9+
fields = [
10+
"certificate_name",
11+
"github_url",
12+
"linkedin_url",
13+
"personal_website_url",
14+
"about_me",
15+
"dark_mode",
16+
]
17+
labels = {
18+
"certificate_name": "Certificate name",
19+
"github_url": "GitHub URL",
20+
"linkedin_url": "LinkedIn URL",
21+
"personal_website_url": "Website URL",
22+
"about_me": "About me",
23+
"dark_mode": "Use dark mode",
24+
}
25+
help_texts = {
26+
"certificate_name": (
27+
"Used for certificates across your course enrollments."
28+
),
29+
}
30+
widgets = {
31+
"certificate_name": forms.TextInput(
32+
attrs={
33+
"class": "form-control",
34+
"placeholder": "Your name for certificates",
35+
}
36+
),
37+
"github_url": forms.TextInput(attrs={"class": "form-control"}),
38+
"linkedin_url": forms.TextInput(attrs={"class": "form-control"}),
39+
"personal_website_url": forms.TextInput(
40+
attrs={"class": "form-control"}
41+
),
42+
"about_me": forms.Textarea(
43+
attrs={
44+
"class": "form-control",
45+
"rows": 3,
46+
"style": "height: 100px;",
47+
}
48+
),
49+
"dark_mode": forms.CheckboxInput(attrs={"class": "h-4 w-4"}),
50+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from django.db import migrations, models
2+
3+
4+
def backfill_certificate_name_from_latest_enrollment(apps, schema_editor):
5+
CustomUser = apps.get_model("accounts", "CustomUser")
6+
Enrollment = apps.get_model("courses", "Enrollment")
7+
db_alias = schema_editor.connection.alias
8+
9+
fields = [
10+
"certificate_name",
11+
"github_url",
12+
"linkedin_url",
13+
"personal_website_url",
14+
"about_me",
15+
]
16+
17+
enrollments = (
18+
Enrollment.objects.using(db_alias)
19+
.order_by("student_id", "-enrollment_date", "-id")
20+
.values("student_id", *fields)
21+
)
22+
23+
profile_updates = {}
24+
for enrollment in enrollments.iterator():
25+
student_id = enrollment["student_id"]
26+
profile = profile_updates.setdefault(student_id, {})
27+
for field in fields:
28+
value = enrollment[field]
29+
if value and field not in profile:
30+
profile[field] = value
31+
32+
for student_id, profile in profile_updates.items():
33+
if profile:
34+
CustomUser.objects.using(db_alias).filter(id=student_id).update(
35+
**profile
36+
)
37+
38+
39+
class Migration(migrations.Migration):
40+
dependencies = [
41+
("accounts", "0004_customuser_dark_mode"),
42+
("courses", "0026_enrollment_disable_learning_in_public_and_more"),
43+
]
44+
45+
operations = [
46+
migrations.AddField(
47+
model_name="customuser",
48+
name="about_me",
49+
field=models.TextField(
50+
blank=True,
51+
null=True,
52+
verbose_name="About me",
53+
),
54+
),
55+
migrations.AddField(
56+
model_name="customuser",
57+
name="github_url",
58+
field=models.URLField(
59+
blank=True,
60+
null=True,
61+
verbose_name="GitHub URL",
62+
),
63+
),
64+
migrations.AddField(
65+
model_name="customuser",
66+
name="linkedin_url",
67+
field=models.URLField(
68+
blank=True,
69+
null=True,
70+
verbose_name="LinkedIn URL",
71+
),
72+
),
73+
migrations.AddField(
74+
model_name="customuser",
75+
name="personal_website_url",
76+
field=models.URLField(
77+
blank=True,
78+
null=True,
79+
verbose_name="Personal website URL",
80+
),
81+
),
82+
migrations.RunPython(
83+
backfill_certificate_name_from_latest_enrollment,
84+
migrations.RunPython.noop,
85+
),
86+
]

accounts/models.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,26 @@ class CustomUser(AbstractUser):
1818
null=True,
1919
help_text="Your actual name that will appear on your certificates"
2020
)
21+
github_url = models.URLField(
22+
verbose_name="GitHub URL",
23+
blank=True,
24+
null=True,
25+
)
26+
linkedin_url = models.URLField(
27+
verbose_name="LinkedIn URL",
28+
blank=True,
29+
null=True,
30+
)
31+
personal_website_url = models.URLField(
32+
verbose_name="Personal website URL",
33+
blank=True,
34+
null=True,
35+
)
36+
about_me = models.TextField(
37+
verbose_name="About me",
38+
blank=True,
39+
null=True,
40+
)
2141
dark_mode = models.BooleanField(
2242
verbose_name="Dark mode",
2343
default=False,
@@ -39,4 +59,4 @@ def save(self, *args, **kwargs):
3959
super().save(*args, **kwargs)
4060

4161
def __str__(self):
42-
return self.key
62+
return self.key
Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,49 @@
1-
{% extends 'base.html' %} {% block title %}Login - Course Management{% endblock %} {% block content %}
2-
<section class="mx-auto max-w-md"> <div class="rounded-md border app-border app-surface "> <div class="border-b app-border app-surface-muted px-5 py-4 text-center "> <h1 class="text-2xl font-semibold app-heading"><i class="fas fa-sign-in-alt"></i> Sign In</h1> <p class="mt-1 text-sm app-muted">Choose your preferred login method</p> </div> <div class="space-y-3 px-5 py-5"> {% for provider in providers %} <a href="{{ provider.login_url }}" class="primer-button primer-button-secondary w-full justify-center"> {% if 'Google' in provider.name %} <i class="fab fa-google text-danger"></i> {% elif 'GitHub' in provider.name %} <i class="fab fa-github text-dark"></i> {% elif 'Slack' in provider.name %} <i class="fab fa-slack text-info"></i> {% else %} <i class="fas fa-sign-in-alt"></i> {% endif %} Continue with {{ provider.name }} </a> {% empty %} <div class="rounded-md border app-alert-warning px-4 py-3 text-center text-sm "> <i class="fas fa-exclamation-triangle"></i> <strong>No login providers configured</strong><br> Please contact an administrator to set up social login. </div> <a href="{% url 'course_list' %}" class="primer-button primer-button-secondary w-full justify-center">Back to courses</a> {% endfor %} </div> {% if providers %} <div class="border-t app-border px-5 py-3 text-center text-xs app-muted "> <i class="fas fa-shield-alt"></i> Secure login - we never store your social media passwords </div> {% endif %} </div>
1+
{% extends 'base.html' %}
2+
3+
{% block title %}Login - Course Management{% endblock %}
4+
5+
{% block content %}
6+
<section class="mx-auto max-w-md">
7+
<div class="rounded-md border app-border app-surface">
8+
<div class="border-b app-border app-surface-muted px-5 py-4 text-center">
9+
<h1 class="text-2xl font-semibold app-heading">
10+
<i class="fas fa-sign-in-alt"></i> Sign In
11+
</h1>
12+
<p class="mt-1 text-sm app-muted">Choose your preferred login method</p>
13+
</div>
14+
15+
<div class="flex flex-col items-start gap-3 px-5 py-5">
16+
{% for provider in providers %}
17+
<a href="{{ provider.login_url }}" class="primer-button primer-button-secondary">
18+
{% if 'Google' in provider.name %}
19+
<i class="fab fa-google text-danger"></i>
20+
{% elif 'GitHub' in provider.name %}
21+
<i class="fab fa-github text-dark"></i>
22+
{% elif 'Slack' in provider.name %}
23+
<i class="fab fa-slack text-info"></i>
24+
{% else %}
25+
<i class="fas fa-sign-in-alt"></i>
26+
{% endif %}
27+
Continue with {{ provider.name }}
28+
</a>
29+
{% empty %}
30+
<div class="rounded-md border app-alert-warning px-4 py-3 text-center text-sm">
31+
<i class="fas fa-exclamation-triangle"></i>
32+
<strong>No login providers configured</strong><br>
33+
Please contact an administrator to set up social login.
34+
</div>
35+
<a href="{% url 'course_list' %}" class="primer-button primer-button-secondary">
36+
Back to courses
37+
</a>
38+
{% endfor %}
39+
</div>
40+
41+
{% if providers %}
42+
<div class="border-t app-border px-5 py-3 text-center text-xs app-muted">
43+
<i class="fas fa-shield-alt"></i>
44+
Secure login - we never store your social media passwords
45+
</div>
46+
{% endif %}
47+
</div>
348
</section>
449
{% endblock %}

0 commit comments

Comments
 (0)