Merge branch 'main' into feature/auto-populate-data

This commit is contained in:
Adil Mohak 2024-02-09 20:59:42 +03:00 committed by GitHub
commit 14448249c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 123 additions and 118 deletions

View File

@ -6,39 +6,39 @@ Let's enhance the project by contributing! 👩‍💻👩‍💻
![Screenshot from 2023-12-31 17-36-31](https://github.com/adilmohak/django-lms/assets/60693922/e7fb628a-6275-4160-ae0f-ab27099ab3ca)
Current features
----------------
* Dashboard: School demographics and analytics. Restricted to only admins
* News And Events: All users can access this page
* Admin manages students(Add, Update, Delete)
* Admin manages lecturers(Add, Update, Delete)
* Students can Add and Drop courses
* Lecturers submit students' scores: _Attendance, Mid exam, Final exam, assignment_
* The system calculates students' _Total, average, point, and grades automatically_
* Grade comment for each student with a **pass**, **fail**, or **pass with a warning**
* Assessment result page for students
* Grade result page for students
* Session/year and semester management
* Assessments and grades will be grouped by semester
* Upload video and documentation for each course
* PDF generator for students' registration slip and grade result
* Page access restriction
* Storing of quiz results under each user
* Question order randomization
* Previous quiz scores can be viewed on the category page
* Correct answers can be shown after each question or all at once at the end
* Logged-in users can return to an incomplete quiz to finish it and non-logged-in users can complete a quiz if their session persists
* The quiz can be limited to one attempt per user
* Questions can be given a category
* Success rate for each category can be monitored on a progress page
* Explanation for each question result can be given
* Pass marks can be set
* Multiple choice question type
* True/False question type
* Essay question type
* Custom message displayed for those that pass or fail a quiz
* Custom permission (view_sittings) added, allowing users with that permission to view quiz results from users
* A marking page which lists completed quizzes, can be filtered by quiz or user, and is used to mark essay questions
## Current features
- Dashboard: School demographics and analytics. Restricted to only admins
- News And Events: All users can access this page
- Admin manages students(Add, Update, Delete)
- Admin manages lecturers(Add, Update, Delete)
- Students can Add and Drop courses
- Lecturers submit students' scores: _Attendance, Mid exam, Final exam, assignment_
- The system calculates students' _Total, average, point, and grades automatically_
- Grade comment for each student with a **pass**, **fail**, or **pass with a warning**
- Assessment result page for students
- Grade result page for students
- Session/year and semester management
- Assessments and grades will be grouped by semester
- Upload video and documentation for each course
- PDF generator for students' registration slip and grade result
- Page access restriction
- Storing of quiz results under each user
- Question order randomization
- Previous quiz scores can be viewed on the category page
- Correct answers can be shown after each question or all at once at the end
- Logged-in users can return to an incomplete quiz to finish it and non-logged-in users can complete a quiz if their session persists
- The quiz can be limited to one attempt per user
- Questions can be given a category
- Success rate for each category can be monitored on a progress page
- Explanation for each question result can be given
- Pass marks can be set
- Multiple choice question type
- True/False question type
- Essay question type
- Custom message displayed for those that pass or fail a quiz
- Custom permission (view_sittings) added, allowing users with that permission to view quiz results from users
- A marking page which lists completed quizzes, can be filtered by quiz or user, and is used to mark essay questions
# Quick note for future contributors
@ -68,14 +68,19 @@ pip install -r requirements.txt
- Create `.env` file inside the root directory and include the following variables
```bash
# Database config
DB_NAME=[YOUR_DB_NAME]
DB_USER=[DB_ADMIN_NAME]
DB_PASSWORD=[DB_ADMIN_PASSWORD]
DB_HOST=localhost
DB_PORT=[YOUR_POSTGRES_PORT default is 5432]
USER_EMAIL=[YOUR_EMAIL]
USER_PASSWORD=[EMAIL_PASSWORD]
EMAIL_FROM_ADDRESS=[THE DEFAULT FROM ADDRESS FOR SENT EMAILS]
# Email config
EMAIL_FROM_ADDRESS=Django LMS <youremail@example.com>
EMAIL_HOST_USER=[YOUR_EMAIL]
EMAIL_HOST_PASSWORD=[YOUR_EMAIL_PASSWORD]
# Other
DEBUG=True
SECRET_KEY=[YOUR_SECRET_KEY]
```
@ -91,6 +96,7 @@ python manage.py runserver
Last but not least, go to this address http://127.0.0.1:8000
### References
- Quiz part: https://github.com/tomwalker/django_quiz
# Connect with me

View File

@ -33,34 +33,42 @@ class LecturerFilter(django_filters.FilterSet):
class StudentFilter(django_filters.FilterSet):
student__username = django_filters.CharFilter(lookup_expr="exact", label="")
student__name = django_filters.CharFilter(method="filter_by_name", label="")
student__email = django_filters.CharFilter(lookup_expr="icontains", label="")
program__title = django_filters.CharFilter(lookup_expr="icontains", label="")
id_no = django_filters.CharFilter(
field_name="student__username", lookup_expr="exact", label=""
)
name = django_filters.CharFilter(
field_name="student__name", method="filter_by_name", label=""
)
email = django_filters.CharFilter(
field_name="student__email", lookup_expr="icontains", label=""
)
program = django_filters.CharFilter(
field_name="student__program", lookup_expr="icontains", label=""
)
class Meta:
model = Student
fields = [
"student__username",
"student__name",
"student__email",
"program__title",
"id_no",
"name",
"email",
"program",
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Change html classes and placeholders
self.filters["student__username"].field.widget.attrs.update(
self.filters["id_no"].field.widget.attrs.update(
{"class": "au-input", "placeholder": "ID No."}
)
self.filters["student__name"].field.widget.attrs.update(
self.filters["name"].field.widget.attrs.update(
{"class": "au-input", "placeholder": "Name"}
)
self.filters["student__email"].field.widget.attrs.update(
self.filters["email"].field.widget.attrs.update(
{"class": "au-input", "placeholder": "Email"}
)
self.filters["program__title"].field.widget.attrs.update(
self.filters["program"].field.widget.attrs.update(
{"class": "au-input", "placeholder": "Program"}
)

View File

@ -118,22 +118,16 @@ class StaffAddForm(UserCreationForm):
user.address = self.cleaned_data.get("address")
user.email = self.cleaned_data.get("email")
# Generate a username based on first and last name and registration date
registration_date = datetime.now().strftime("%Y%m%d%H%M")
# Generate a username
registration_date = datetime.now().strftime("%Y")
total_lecturers_count = User.objects.filter(is_lecturer=True).count()
generated_username = (
f"{user.first_name.lower()}{user.last_name.lower()}{registration_date}"
)
# Check if the generated username already exists, and regenerate if needed
while User.objects.filter(username=generated_username).exists():
registration_date = datetime.now().strftime("%Y%m%d%H%M")
generated_username = f"{user.first_name.lower()}{user.last_name.lower()}{registration_date}".replace(
" ", ""
f"{settings.LECTURER_ID_PREFIX}-{registration_date}-{total_lecturers_count}"
)
# Generate a password
generated_password = User.objects.make_random_password()
user.username = generated_username
generated_password = User.objects.make_random_password()
user.set_password(generated_password)
if commit:
@ -141,7 +135,7 @@ class StaffAddForm(UserCreationForm):
# Send email with the generated credentials
send_mail(
"Your account credentials",
"Your Django LMS account credentials",
f"Your username: {generated_username}\nYour password: {generated_password}",
"from@example.com",
[user.email],
@ -275,7 +269,7 @@ class StudentAddForm(UserCreationForm):
@transaction.atomic()
def save(self, commit=True):
user = super().save(commit=False)
user.is_lecturer = True
user.is_student = True
user.first_name = self.cleaned_data.get("first_name")
user.last_name = self.cleaned_data.get("last_name")
user.gender = self.cleaned_data.get("gender")
@ -285,30 +279,29 @@ class StudentAddForm(UserCreationForm):
user.email = self.cleaned_data.get("email")
# Generate a username based on first and last name and registration date
registration_date = datetime.now().strftime("%Y%m%d%H%M")
registration_date = datetime.now().strftime("%Y")
total_students_count = Student.objects.count()
generated_username = (
f"{user.first_name.lower()}{user.last_name.lower()}{registration_date}"
)
# Check if the generated username already exists, and regenerate if needed
while User.objects.filter(username=generated_username).exists():
registration_date = datetime.now().strftime("%Y%m%d%H%M")
generated_username = f"{user.first_name.lower()}{user.last_name.lower()}{registration_date}".replace(
" ", ""
f"{settings.STUDENT_ID_PREFIX}-{registration_date}-{total_students_count}"
)
# Generate a password
generated_password = User.objects.make_random_password()
user.username = generated_username
generated_password = User.objects.make_random_password()
user.set_password(generated_password)
if commit:
user.save()
Student.objects.create(
student=user,
level=self.cleaned_data.get("level"),
program=self.cleaned_data.get("program"),
)
# Send email with the generated credentials
send_mail(
"Your account credentials",
f"Your username: {generated_username}\nYour password: {generated_password}",
"Your Django LMS account credentials",
f"Your ID: {generated_username}\nYour password: {generated_password}",
settings.EMAIL_FROM_ADDRESS,
[user.email],
fail_silently=False,
@ -348,6 +341,15 @@ class ProfileUpdateForm(UserChangeForm):
label="Last Name",
)
gender = forms.CharField(
widget=forms.Select(
choices=GENDERS,
attrs={
"class": "browser-default custom-select form-control",
},
),
)
phone = forms.CharField(
widget=forms.TextInput(
attrs={
@ -370,7 +372,15 @@ class ProfileUpdateForm(UserChangeForm):
class Meta:
model = User
fields = ["email", "phone", "address", "picture", "first_name", "last_name"]
fields = [
"first_name",
"last_name",
"gender",
"email",
"phone",
"address",
"picture",
]
class EmailValidationOnForgotPassword(PasswordResetForm):

0
accounts/signals.py Normal file
View File

View File

@ -354,6 +354,7 @@ def edit_student(request, pk):
@method_decorator([login_required, admin_required], name="dispatch")
class StudentListView(FilterView):
queryset = Student.objects.all()
filterset_class = StudentFilter
template_name = "accounts/student_list.html"
paginate_by = 10

View File

@ -193,13 +193,11 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media")
EMAIL_BACKEND = config(
"EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend"
)
EMAIL_HOST = config(
"EMAIL_HOST", default="smtp.gmail.com"
) # Gmail as the email host, but you can change it
EMAIL_HOST = config("EMAIL_HOST", default="smtp.gmail.com")
EMAIL_PORT = config("EMAIL_PORT", default=587)
EMAIL_USE_TLS = True
EMAIL_HOST_USER = config("USER_EMAIL")
EMAIL_HOST_PASSWORD = config("USER_PASSWORD")
EMAIL_HOST_USER = config("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD")
EMAIL_FROM_ADDRESS = config("EMAIL_FROM_ADDRESS")
# crispy config
@ -250,3 +248,6 @@ LOGGING = {
# WhiteNoise configuration
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
STUDENT_ID_PREFIX = config("STUDENT_ID_PREFIX", "ugr")
LECTURER_ID_PREFIX = config("LECTURER_ID_PREFIX", "lec")

View File

@ -1,12 +1,11 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.conf import settings
from accounts.decorators import admin_required, lecturer_required
from accounts.models import User, Student
from .forms import SessionForm, SemesterForm, NewsAndEventsForm
from .models import *
from .models import NewsAndEvents, ActivityLog, Session, Semester
# ########################################################
@ -28,9 +27,9 @@ def dashboard_view(request):
logs = ActivityLog.objects.all().order_by("-created_at")[:10]
gender_count = Student.get_gender_count()
context = {
"student_count": User.get_student_count(),
"lecturer_count": User.get_lecturer_count(),
"superuser_count": User.get_superuser_count(),
"student_count": User.objects.get_student_count(),
"lecturer_count": User.objects.get_lecturer_count(),
"superuser_count": User.objects.get_superuser_count(),
"males_count": gender_count["M"],
"females_count": gender_count["F"],
"logs": logs,

View File

@ -27,6 +27,7 @@
{{ form.email|as_crispy_field }}
{{ form.first_name|as_crispy_field }}
{{ form.last_name|as_crispy_field }}
{{ form.gender|as_crispy_field }}
{{ form.phone|as_crispy_field }}
{{ form.address|as_crispy_field }}
</div>

View File

@ -18,20 +18,7 @@
<p class="title-1"><i class="fas fa-chalkboard-teacher"></i>Lecturers</p>
{% if messages %}
{% for message in messages %}
{% if message.tags == 'error' %}
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>{{ message }}
</div>
{% else %}
<div class="alert alert-success">
<i class="fas fa-check-circle"></i>{{ message }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% include 'snippets/messages.html' %}
{% include 'snippets/filter_form.html' %}
<div class="table-responsive table-shadow table-light table-striped m-0 mt-4">

View File

@ -22,20 +22,8 @@
<div class="title-1"><i class="fas fa-user-graduate"></i>Students</div>
<br>
<br>
{% if messages %}
{% for message in messages %}
{% if message.tags == 'error' %}
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i>{{ message }}
</div>
{% else %}
<div class="alert alert-success">
<i class="fas fa-check-circle"></i>{{ message }}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% include 'snippets/messages.html' %}
{% include 'snippets/filter_form.html' %}
<div class="table-responsive table-shadow table-light table-striped m-0 mt-4">

View File

@ -305,6 +305,9 @@
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const malesCount = {{ males_count }}
const femalesCount = {{ females_count }}
$(document).ready(function () {
// Setup
@ -478,7 +481,7 @@
],
datasets: [{
label: "Students Gender Dataset",
data: [{{ males_count }}, {{ females_count }}],
data: [malesCount, femalesCount],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)'

View File

@ -25,6 +25,7 @@
{{ form.email|as_crispy_field }}
{{ form.first_name|as_crispy_field }}
{{ form.last_name|as_crispy_field }}
{{ form.gender|as_crispy_field }}
{{ form.phone|as_crispy_field }}
{{ form.address|as_crispy_field }}
</div>