initial commit

This commit is contained in:
Adil Mohak 2021-03-25 16:13:17 +03:00
parent 30af5f4735
commit f6ab9e8128
141 changed files with 10656 additions and 0 deletions

93
.gitignore vendored Normal file
View File

@ -0,0 +1,93 @@
# Created by https://www.gitignore.io
### OSX ###
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear on external disk
.Spotlight-V100
.Trashes
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Sphinx documentation
docs/_build/
# PyBuilder
target/
### Django ###
*.log
*.pot
*.pyc
__pycache__/
local_settings.py
.vscode
.env
db.sqlite3
media
static

68
README.md Normal file
View File

@ -0,0 +1,68 @@
# Advanced student management and e-learning system
My portfolio website -> https://ezop.herokuapp.com
I build this student management and e-learning system using django and Bootstrap for the front-end. You can watch the video on YouTube to find out how the app works in real time https://youtu.be/ytP-k95F0ug
For the quiz part, I used this repo as a reference -> https://github.com/tomwalker/django_quiz
Quizzes
![Screenshot (123)](https://user-images.githubusercontent.com/60693922/112447687-a802cf80-8d62-11eb-85f5-aebf9164d03a.png)
Current features
----------------
* News And Events
* The admin can Add Students
* The admin can Add Lecturers
* Students can Add and Drop courses
* Lecturers submit students score (Attendance, Mid exam, Final exam, assignment)
* The system calculates the Total, Avarage, point, and grade of each student then save it
* Also, the system tells the student whether he/she pass, fail or pass with a warning
* Assessment result
* Grade result
* Documentations
* Video Tutorials
* PDF generator for students registration slip
* PDF generator for students result
* Storing of quiz results under each user
* Question order randomisation
* Previous quiz scores can be viewed on 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
* Another cool feature of the app is, unnecessary files are removed automatically for memory efficiency
![Screenshot (124)](https://user-images.githubusercontent.com/60693922/112449252-3f1c5700-8d64-11eb-8549-bfe52122adf3.png)
![Screenshot (118)](https://user-images.githubusercontent.com/60693922/112449489-7d197b00-8d64-11eb-9ed2-ed7dcd2fe89d.png)
![Screenshot (126)](https://user-images.githubusercontent.com/60693922/112449542-8b679700-8d64-11eb-8ff8-4320a720a3d7.png)
![Screenshot (122)](https://user-images.githubusercontent.com/60693922/112449435-6ecb5f00-8d64-11eb-9d34-4dc3473a5312.png)
After the student finished the quiz, here is how the result display
![Screenshot (127)](https://user-images.githubusercontent.com/60693922/89736959-1d40bf00-da76-11ea-98a8-b9e95db4da77.png)
# Installation
First Clone the repo with `git clone https://github.com/adilmohak/django_sms_and_e-learning.git`
Run the following commands
`pip install -r requirements.txt`
`python manage.py runserver`
Finally go to this address http://127.0.0.1:8000
# Thank You!!

0
SMS/__init__.py Normal file
View File

159
SMS/settings.py Normal file
View File

@ -0,0 +1,159 @@
"""
Django settings for SMS project.
Generated by 'django-admin startproject' using Django 2.2.3.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""
import os
import posixpath
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'o!ld8nrt4vc*h1zoey*wj48x*q0#ss12h=+zh)kk^6b3aygg=!'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# change the default user models to our custom model
AUTH_USER_MODEL = 'accounts.User'
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_cleanup',
# custom apps
'app.apps.AppConfig',
'accounts.apps.AccountsConfig',
'course.apps.CourseConfig',
'result.apps.ResultConfig',
'search.apps.SearchConfig',
'quiz.apps.QuizConfig',
'crispy_forms'
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'SMS.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
# 'django.template.context_processors.i18n',
# 'django.template.context_processors.media',
# 'django.template.context_processors.static',
# 'django.template.context_processors.tz',
],
},
},
]
WSGI_APPLICATION = 'SMS.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = posixpath.join(*(BASE_DIR.split(os.path.sep) + ['staticfiles']))
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.environ.get('USER_EMAIL')
EMAIL_HOST_PASSWORD = os.environ.get('USER_PASSWORD')
CRISPY_TEMPLATE_PACK = 'bootstrap4'
LOGIN_REDIRECT_URL = '/'
LOGOUT_REDIRECT_URL = '/'

27
SMS/urls.py Normal file
View File

@ -0,0 +1,27 @@
from django.contrib import admin
from django.conf.urls import url
from django.urls import path, include
from django.conf.urls import handler404, handler500, handler400
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')),
url(r'^', include('app.urls')),
url(r'^accounts/', include('accounts.urls')),
url(r'^programs/', include('course.urls')),
url(r'^result/', include('result.urls')),
url(r'^search/', include('search.urls')),
url(r'^quiz/', include('quiz.urls')),
url(r'^admin/', admin.site.urls),
]
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# handler404 = 'app.views.handler404'
# handler500 = 'app.views.handler500'
# handler400 = 'app.views.handler400'

16
SMS/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for SMS project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SMS.settings')
application = get_wsgi_application()

0
accounts/__init__.py Normal file
View File

28
accounts/admin.py Normal file
View File

@ -0,0 +1,28 @@
from django.contrib import admin
from django.contrib.auth.models import Group
from .models import User, Student, Parent
class UserAdmin(admin.ModelAdmin):
list_display = ['get_full_name', 'username', 'email', 'is_active', 'is_student', 'is_lecturer', 'is_parent', 'is_staff']
search_fields = ['username', 'first_name', 'last_name', 'email', 'is_active', 'is_lecturer', 'is_parent', 'is_staff']
class Meta:
managed = True
verbose_name = 'User'
verbose_name_plural = 'Users'
# class ScoreAdmin(admin.ModelAdmin):
# list_display = [
# 'student', 'course', 'assignment', 'mid_exam', 'quiz',
# 'attendance', 'final_exam', 'total', 'grade', 'comment'
# ]
admin.site.register(User, UserAdmin)
admin.site.register(Student)
admin.site.register(Parent)
# admin.site.unregister(Group)

5
accounts/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'

48
accounts/decorators.py Normal file
View File

@ -0,0 +1,48 @@
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.http import Http404
from django.contrib.auth.decorators import user_passes_test
def student_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=Http404):
"""
Decorator for views that checks that the logged in user is a student,
redirects to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_active and u.is_student or u.is_superuser,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
def lecturer_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=Http404):
"""
Decorator for views that checks that the logged in user is a teacher,
redirects to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_active and u.is_lecturer or u.is_superuser,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
def admin_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=Http404):
"""
Decorator for views that checks that the logged in user is a teacher,
redirects to the log-in page if necessary.
"""
actual_decorator = user_passes_test(
lambda u: u.is_active and u.is_superuser,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator

322
accounts/forms.py Normal file
View File

@ -0,0 +1,322 @@
from django import forms
from django.db import transaction
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordChangeForm
# from django.contrib.auth.models import User
from django.contrib.auth.forms import PasswordResetForm
from course.models import Program
# from .models import User, Student, LEVEL
from .models import *
class StaffAddForm(UserCreationForm):
username = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Username", )
first_name = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="First Name", )
last_name = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Last Name", )
address = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Address", )
phone = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Mobile No.", )
email = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Email", )
password1 = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'password', 'class': 'form-control', }),
label="Password", )
password2 = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'password', 'class': 'form-control', }),
label="Password Confirmation", )
class Meta(UserCreationForm.Meta):
model = User
@transaction.atomic()
def save(self, commit=True):
user = super().save(commit=False)
user.is_lecturer = True
user.first_name = self.cleaned_data.get('first_name')
user.last_name = self.cleaned_data.get('last_name')
user.phone = self.cleaned_data.get('phone')
user.address = self.cleaned_data.get('address')
user.email = self.cleaned_data.get('email')
if commit:
user.save()
return user
class StudentAddForm(UserCreationForm):
username = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Username",
)
address = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Address",
)
phone = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Mobile No.",
)
first_name = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="First name",
)
last_name = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Last name",
)
level = forms.CharField(
widget=forms.Select(
choices=LEVEL,
attrs={
'class': 'browser-default custom-select form-control',
}
),
)
department = forms.ModelChoiceField(
queryset=Program.objects.all(),
widget=forms.Select(attrs={'class': 'browser-default custom-select form-control'}),
label="Department",
)
email = forms.EmailField(
widget=forms.TextInput(
attrs={
'type': 'email',
'class': 'form-control',
}
),
label="Email Address",
)
password1 = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'password', 'class': 'form-control', }),
label="Password", )
password2 = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'password', 'class': 'form-control', }),
label="Password Confirmation", )
# def validate_email(self):
# email = self.cleaned_data['email']
# if User.objects.filter(email__iexact=email, is_active=True).exists():
# raise forms.ValidationError("Email has taken, try another email address. ")
class Meta(UserCreationForm.Meta):
model = User
@transaction.atomic()
def save(self):
user = super().save(commit=False)
user.is_student = True
user.first_name = self.cleaned_data.get('first_name')
user.last_name = self.cleaned_data.get('last_name')
user.address = self.cleaned_data.get('address')
user.phone = self.cleaned_data.get('phone')
user.email = self.cleaned_data.get('email')
user.save()
student = Student.objects.create(
student=user,
level=self.cleaned_data.get('level'),
department=self.cleaned_data.get('department')
)
student.save()
return user
class ProfileUpdateForm(UserChangeForm):
email = forms.EmailField(
widget=forms.TextInput(attrs={'type': 'email', 'class': 'form-control', }),
label="Email Address", )
first_name = forms.CharField(
widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="First Name", )
last_name = forms.CharField(
widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Last Name", )
phone = forms.CharField(
widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Phone No.", )
address = forms.CharField(
widget=forms.TextInput(attrs={'type': 'text', 'class': 'form-control', }),
label="Address / city", )
class Meta:
model = User
fields = ['email', 'phone', 'address', 'picture', 'first_name', 'last_name']
class EmailValidationOnForgotPassword(PasswordResetForm):
def clean_email(self):
email = self.cleaned_data['email']
if not User.objects.filter(email__iexact=email, is_active=True).exists():
msg = "There is no user registered with the specified E-mail address. "
self.add_error('email', msg)
return email
class ParentAddForm(UserCreationForm):
username = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Username",
)
address = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Address",
)
phone = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Mobile No.",
)
first_name = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="First name",
)
last_name = forms.CharField(
max_length=30,
widget=forms.TextInput(
attrs={
'type': 'text',
'class': 'form-control',
}
),
label="Last name",
)
email = forms.EmailField(
widget=forms.TextInput(
attrs={
'type': 'email',
'class': 'form-control',
}
),
label="Email Address",
)
student = forms.ModelChoiceField(
queryset=Student.objects.all(),
widget=forms.Select(attrs={'class': 'browser-default custom-select form-control'}),
label="Student",
)
relation_ship = forms.CharField(
widget=forms.Select(
choices=RELATION_SHIP,
attrs={'class': 'browser-default custom-select form-control',}
),
)
password1 = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'password', 'class': 'form-control', }),
label="Password", )
password2 = forms.CharField(
max_length=30, widget=forms.TextInput(attrs={'type': 'password', 'class': 'form-control', }),
label="Password Confirmation", )
# def validate_email(self):
# email = self.cleaned_data['email']
# if User.objects.filter(email__iexact=email, is_active=True).exists():
# raise forms.ValidationError("Email has taken, try another email address. ")
class Meta(UserCreationForm.Meta):
model = User
@transaction.atomic()
def save(self):
user = super().save(commit=False)
user.is_parent = True
user.first_name = self.cleaned_data.get('first_name')
user.last_name = self.cleaned_data.get('last_name')
user.address = self.cleaned_data.get('address')
user.phone = self.cleaned_data.get('phone')
user.email = self.cleaned_data.get('email')
user.save()
parent = Parent.objects.create(
user=user,
student=self.cleaned_data.get('student'),
relation_ship=self.cleaned_data.get('relation_ship')
)
parent.save()
return user

View File

@ -0,0 +1,53 @@
# Generated by Django 2.2.3 on 2020-07-29 15:25
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('is_student', models.BooleanField(default=False)),
('is_lecturer', models.BooleanField(default=False)),
('phone', models.CharField(blank=True, max_length=60, null=True)),
('address', models.CharField(blank=True, max_length=60, null=True)),
('picture', models.ImageField(default='default.png', null=True, upload_to='profile_pictures')),
('email', models.EmailField(blank=True, max_length=254, null=True)),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Student',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('level', models.CharField(choices=[('Level course', 'Level course'), ('Bachloar', 'Bachloar'), ('Master', 'Master')], max_length=25, null=True)),
],
),
]

View File

@ -0,0 +1,39 @@
# Generated by Django 2.2.3 on 2020-07-29 15:25
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('accounts', '0001_initial'),
('auth', '0011_update_proxy_permissions'),
('course', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='student',
name='department',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='course.Program'),
),
migrations.AddField(
model_name='student',
name='student',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='user',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
),
migrations.AddField(
model_name='user',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2020-07-30 04:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_auto_20200729_1825'),
]
operations = [
migrations.AlterField(
model_name='student',
name='level',
field=models.CharField(choices=[('Bachloar', 'Bachloar Degree'), ('Master', 'Master Degree')], max_length=25, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2020-08-22 19:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0003_auto_20200730_0740'),
]
operations = [
migrations.AlterField(
model_name='user',
name='picture',
field=models.ImageField(default='default.png', null=True, upload_to='profile_pictures/%y/%m/%d/'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2020-08-22 19:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_auto_20200822_2238'),
]
operations = [
migrations.AlterField(
model_name='user',
name='picture',
field=models.ImageField(blank=True, null=True, upload_to='profile_pictures/%y/%m/%d/'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2020-08-22 20:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0005_auto_20200822_2246'),
]
operations = [
migrations.AlterField(
model_name='user',
name='picture',
field=models.ImageField(default='default.png', null=True, upload_to='profile_pictures/%y/%m/%d/'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-08-25 09:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0006_auto_20200822_2308'),
]
operations = [
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
),
]

View File

@ -0,0 +1,38 @@
# Generated by Django 2.2 on 2020-08-31 10:15
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0007_auto_20200825_1248'),
]
operations = [
migrations.AddField(
model_name='user',
name='is_parent',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='user',
name='first_name',
field=models.CharField(blank=True, max_length=30, verbose_name='first name'),
),
migrations.CreateModel(
name='Family',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('first_name', models.CharField(max_length=120)),
('last_name', models.CharField(max_length=120)),
('phone', models.CharField(blank=True, max_length=60, null=True)),
('address', models.CharField(blank=True, max_length=60, null=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('relation_ship', models.TextField(blank=True, choices=[('Father', 'Father'), ('Mother', 'Mother'), ('Brother', 'Brother'), ('Sister', 'Sister'), ('Grand mother', 'Grand mother'), ('Grand father', 'Grand father'), ('Other', 'Other')])),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2 on 2020-09-06 11:03
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('accounts', '0008_auto_20200831_1315'),
]
operations = [
migrations.CreateModel(
name='Parent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('relation_ship', models.TextField(blank=True, choices=[('Father', 'Father'), ('Mother', 'Mother'), ('Brother', 'Brother'), ('Sister', 'Sister'), ('Grand mother', 'Grand mother'), ('Grand father', 'Grand father'), ('Other', 'Other')])),
('student', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='accounts.Student')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.DeleteModel(
name='Family',
),
]

View File

161
accounts/models.py Normal file
View File

@ -0,0 +1,161 @@
from django.db import models
from django.urls import reverse
from django.contrib.auth.models import AbstractUser
from django.conf import settings
from django.db.models import Q
from PIL import Image
from course.models import Program
from .validators import ASCIIUsernameValidator
User = settings.AUTH_USER_MODEL
# LEVEL_COURSE = "Level course"
BACHLOAR_DEGREE = "Bachloar"
MASTER_DEGREE = "Master"
LEVEL = (
# (LEVEL_COURSE, "Level course"),
(BACHLOAR_DEGREE, "Bachloar Degree"),
(MASTER_DEGREE, "Master Degree"),
)
FATHER = "Father"
MOTHER = "Mother"
BROTHER = "Brother"
SISTER = "Sister"
GRAND_MOTHER = "Grand mother"
GRAND_FATHER = "Grand father"
OTHER = "Other"
RELATION_SHIP = (
(FATHER, "Father"),
(MOTHER, "Mother"),
(BROTHER, "Brother"),
(SISTER, "Sister"),
(GRAND_MOTHER, "Grand mother"),
(GRAND_FATHER, "Grand father"),
(OTHER, "Other"),
)
# class UserManager(models.Manager):
# def search(self, query=None):
# qs = self.get_queryset()
# if query is not None:
# or_lookup = (Q(username__icontains=query) |
# Q(first_name__icontains=query)|
# Q(last_name__icontains=query)|
# Q(email__icontains=query)
# )
# qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
# return qs
class User(AbstractUser):
is_student = models.BooleanField(default=False)
is_lecturer = models.BooleanField(default=False)
is_parent = models.BooleanField(default=False)
phone = models.CharField(max_length=60, blank=True, null=True)
address = models.CharField(max_length=60, blank=True, null=True)
picture = models.ImageField(upload_to='profile_pictures/%y/%m/%d/', default='default.png', null=True)
email = models.EmailField(blank=True, null=True)
username_validator = ASCIIUsernameValidator()
# objects = UserManager()
@property
def get_full_name(self):
full_name = self.username
if self.first_name and self.last_name:
full_name = self.first_name + " " + self.last_name
return full_name
def __str__(self):
return '{} ({})'.format(self.username, self.get_full_name)
@property
def get_user_role(self):
if self.is_superuser:
return "Admin"
elif self.is_student:
return "Student"
elif self.is_lecturer:
return "Lecturer"
elif self.is_parent:
return "Parent"
def get_picture(self):
no_picture = settings.MEDIA_URL + 'default.png'
try:
return self.picture.url
except:
return no_picture
def get_absolute_url(self):
return reverse('profile_single', kwargs={'id': self.id})
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
try:
img = Image.open(self.picture.path)
if img.height > 300 or img.width > 300:
output_size = (300, 300)
img.thumbnail(output_size)
img.save(self.picture.path)
except:
pass
def delete(self, *args, **kwargs):
if self.picture.url != settings.MEDIA_URL + 'default.png':
self.picture.delete()
super().delete(*args, **kwargs)
class StudentManager(models.Manager):
def search(self, query=None):
qs = self.get_queryset()
if query is not None:
or_lookup = (Q(level__icontains=query) |
Q(department__icontains=query)
)
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
return qs
class Student(models.Model):
student = models.OneToOneField(User, on_delete=models.CASCADE)
# id_number = models.CharField(max_length=20, unique=True, blank=True)
level = models.CharField(max_length=25, choices=LEVEL, null=True)
department = models.ForeignKey(Program, on_delete=models.CASCADE, null=True)
objects = StudentManager()
def __str__(self):
return self.student.get_full_name
def get_absolute_url(self):
return reverse('profile_single', kwargs={'id': self.id})
def delete(self, *args, **kwargs):
self.student.delete()
super().delete(*args, **kwargs)
# class Parent(models.Model):
# parent = models.
class Parent(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
student = models.OneToOneField(Student, null=True, on_delete=models.SET_NULL)
# first_name = models.CharField(max_length=120)
# last_name = models.CharField(max_length=120)
# phone = models.CharField(max_length=60, blank=True, null=True)
# address = models.CharField(max_length=60, blank=True, null=True)
# email = models.EmailField(blank=True, null=True)
relation_ship = models.TextField(choices=RELATION_SHIP, blank=True)
def __str__(self):
return self.user.username

View File

@ -0,0 +1,87 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'lecturer_list' %}" class="primary1">Lecturers</a> <i>&rsaquo;</i> Add</div>
<br>
<div class="title-1"><i class="fas fa-chalkboard-teacher"></i>Lecturer Add Form</div>
<div class="title-line"></div><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 %}
<form action="" method="POST">{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<p class="form-title">Login Info</p>
<div class="p-2">
<div class="form-group">
<b>ID No.</b>{{ form.username }}
<span class="danger">{{ form.username.errors }}</span>
<p class="text-muted-xs">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</p>
</div>
<div class="form-group">
<b>{{ form.password1.label }}</b>{{ form.password1 }}
<span class="danger">{{ form.password1.errors }}</span>
<p class="text-muted-xs">
The password can't be too similar to other personal information.<br>
The password must contain at least 8 characters.<br>
The password can't be a commonly used password.<br>
The password can't be entirely numeric.
</p>
</div>
<div class="form-group">
<b>{{ form.password2.label }}</b>{{ form.password2 }}
<span class="danger">{{ form.password2.errors }}</span>
<p class="text-muted-xs">Enter the same password as before, for verification.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<p class="form-title">Personal Info</p>
<div class="p-2">
<div class="form-group">
<b>{{ form.first_name.label }}</b>{{ form.first_name }}
<span class="danger">{{ form.first_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.last_name.label }}</b>{{ form.last_name }}
<span class="danger">{{ form.last_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.email.label }}</b>{{ form.email }}
<span class="danger">{{ form.email.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.address.label }}</b>{{ form.address }}
<span class="danger">{{ form.address.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.phone.label }}</b>{{ form.phone }}
<span class="danger">{{ form.phone.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<center><input class="btn btn-outline-primary" type="submit" value="Save"></center>
</form>
{% endblock content %}

View File

@ -0,0 +1,110 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'student_list' %}" class="primary1">Students</a> <i>&rsaquo;</i> Add</div>
<br>
<div class="title-1"><i class="fas fa-user-graduate"></i>Student Add Form</div>
<div class="title-line"></div><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 %}
<form action="" method="POST">{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<p class="form-title">Login Info</p>
<div class="p-3">
<div class="form-group">
<b>ID No.</b>{{ form.username }}
<span class="danger">{{ form.username.errors }}</span>
<p class="text-muted-xs">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</p>
</div>
<div class="form-group">
<b>{{ form.password1.label }}</b>{{ form.password1 }}
<span class="danger">{{ form.password1.errors }}</span>
<p class="text-muted-xs">
The password can't be too similar to other personal information.<br>
The password must contain at least 8 characters.<br>
The password can't be a commonly used password.<br>
The password can't be entirely numeric.
</p>
</div>
<div class="form-group">
<b>{{ form.password2.label }}</b>{{ form.password2 }}
<span class="danger">{{ form.password2.errors }}</span>
<p class="text-muted-xs">Enter the same password as before, for verification.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<p class="form-title">Personal Info</p>
<div class="container">
<div class="form-group">
<b>{{ form.first_name.label }}</b>{{ form.first_name }}
<span class="danger">{{ form.first_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.last_name.label }}</b>{{ form.last_name }}
<span class="danger">{{ form.last_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.email.label }}</b>{{ form.email }}
<span class="danger">{{ form.email.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.address.label }}</b>{{ form.address }}
<span class="danger">{{ form.address.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.phone.label }}</b>{{ form.phone }}
<span class="danger">{{ form.phone.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6 mr-auto">
<div class="card">
<p class="form-title">Others</p>
<div class="p-3">
<div class="form-group">
<b>{{ form.department.label }}</b>{{ form.department }}
<span class="danger">{{ form.department.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.level.label }}</b>{{ form.level }}
<span class="danger">{{ form.level.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<center><input class="btn btn-outline-primary" type="submit" value="Save"></center>
</form>
{% endblock content %}

View File

@ -0,0 +1,72 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'lecturer_list' %}" class="primary1">Lecturers </a><i>&rsaquo;</i> setting</div>
<p class="title-1"><i class="fas fa-cogs"></i>Lecturer Update Form</p>
<div class="title-line"></div><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 %}
<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<p class="form-title">Email & Personal Info</p>
<div class="p-3">
<div class="form-group">
<b>{{ form.email.label }}</b>{{ form.email }}
<span class="danger">{{ form.email.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.first_name.label }}</b>{{ form.first_name }}
<span class="danger">{{ form.first_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.last_name.label }}</b>{{ form.last_name }}
<span class="danger">{{ form.last_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.phone.label }}</b>{{ form.phone }}
<span class="danger">{{ form.phone.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.address.label }}</b>{{ form.address }}
<span class="danger">{{ form.address.errors }}</span>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<p class="form-title">Others</p>
<div class="p-3">
<div class="form-group">
<b>Profile Picture</b><br>{{ form.picture }}
<span class="danger">{{ form.picture.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<center><input class="btn btn-outline-primary" type="submit" value="Save"></center>
</form>
{% endblock content %}

View File

@ -0,0 +1,73 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}<br>
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'student_list' %}" class="primary1">Students </a><i>&rsaquo;</i> Account setting
</div>
<p class="title-1"><i class="fas fa-cogs"></i>Student Update Form</p>
<div class="title-line"></div><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 %}
<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
<div class="card">
<p class="form-title">Email & Personal Info</p>
<div class="p-3">
<div class="form-group">
<b>{{ form.email.label }}</b>{{ form.email }}
<span class="danger">{{ form.email.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.first_name.label }}</b>{{ form.first_name }}
<span class="danger">{{ form.first_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.last_name.label }}</b>{{ form.last_name }}
<span class="danger">{{ form.last_name.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.phone.label }}</b>{{ form.phone }}
<span class="danger">{{ form.phone.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.address.label }}</b>{{ form.address }}
<span class="danger">{{ form.address.errors }}</span>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<p class="form-title">Others</p>
<div class="p-3">
<div class="form-group">
<b>Profile Picture</b><br>{{ form.picture }}
<span class="danger">{{ form.picture.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<center><input class="btn btn-outline-primary" type="submit" value="Save"></center>
</form>
{% endblock content %}

View File

@ -0,0 +1,119 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Lecturers</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'add_lecturer' %}"><i class="fas fa-plus"></i>Add Lecturer</a>
{% endif %}
<p class="title-1"><i class="fas fa-chalkboard-teacher"></i>Lecturers List</p>
<div class="title-line"></div>
{% 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 %}
<div class="content-center">
<form class="search-form" action="" method="POST">{% csrf_token %}
<input class="au-input" type="text" name="id_no" placeholder="ID No." value="{{ request.GET.id_no }}"/>
<input class="au-input" type="text" name="name" placeholder="Name" value="{{ request.GET.name }}"/>
<input class="au-input" type="text" name="email" placeholder="Email" value="{{ request.GET.email }}"/>
<button class="btn btn-light" type="submit">
<i class="fas fa-search"></i> filter
</button>
</form>
</div>
<div class="table-responsive table-shadow p-0 mt-5">
<table class="table">
<thead>
<tr>
<th>#</th>
<th> ID No. </th>
<th> Full Name </th>
<th> Email </th>
<th> Mob No. </th>
<th> Address/city </th>
<th> Last login </th>
{% if request.user.is_superuser %}
<th> Action </th>
{% endif %}
</tr>
</thead>
<tbody>
{% for lecturer in object_list %}
<tr>
<td> {{ forloop.counter }}.</td>
<td>{{ lecturer.username }}</td>
<td><a href="{% url 'profile_single' lecturer.id %}">{{ lecturer.get_full_name }}</a></td>
<td>{{ lecturer.email }}</td>
<td>{{ lecturer.phone }}</td>
<td>{{ lecturer.address }}</td>
<td>{{ lecturer.last_login }}</td>
{% if request.user.is_superuser %}
<td>
<div class="update-delete">
<a href="{% url 'staff_edit' pk=lecturer.pk %}" class="update"><i class="fas fa-edit"></i></a>
<form action="{% url 'lecturer_delete' pk=lecturer.pk %}">{% csrf_token %}
<button type="submit" class="delete"><i class="fas fa-trash-alt"></i></button>
</form>
</div>
</td>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Lecturer(s).
{% if request.user.is_superuser %}
<a href="{% url 'add_lecturer' %}">
<i class="primary" style="font-size: 22px;">
Add Lecturer Now.
</i>
{% endif %}
</a>
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}
{% block js %}
<script>
const xhr = new XMLHttpRequest()
const method = 'GET'
const url = "/accounts/lecturers/"
const responseType = "json"
xhr.responseType = responseType
xhr.open(method, url)
xhr.onload = function() {
console.log(xhr.response)
}
xhr.send()
</script>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<form method="POST">{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="" value="Save">
</form>
{% endblock %}

View File

@ -0,0 +1,129 @@
{% extends 'base.html' %}
{% block title %} {{ title }} | DjangoSMS{% endblock title %}
{% load static %}
{% load i18n %}
{% block content %}
{% if user.is_authenticated %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> {{ user.get_full_name }}</div>
{% 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 %}
<div class="btn-flex">
<a class="edit-btn" href="{% url 'edit_profile' %}"><i class="fas fa-user-edit"></i>
<span class="mobile-hide">Edit Profile</span></a>
<a class="edit-btn" href="{% url 'change_password' %}"><i class="fas fa-lock"></i><span class="mobile-hide">
Change password</span></a>
</div>
<div class="row">
<div class="col-md-3 mx-auto">
<div class="card">
<img src="{{ user.picture.url }}" class="dashboard-img">
<div class="program-description">
<p><strong>Last login: </strong>{{ user.last_login|date }}</p>
<p><strong>Full Name: </strong>{{ user.get_full_name|title }}</p>
<p><strong>Role: </strong>
{{ user.get_user_role }}
</p>
</div>
</div>
</div>
<div class="col-md-6 mx-auto">
<div class="card">
<!-- {% if user.is_student %}
<p class="form-title"><i class="fas fa-book-open"></i>Enrolled Courses</p>
<div class="dashboard-description">
{% if courses %}
<div class="flex">
{% for course in courses %}
<div class="flex"><a class="edit-btn" href="{{ course.get_absolute_url }}">{{ course }}</a></div>
{% endfor %}
</div>
{% else %}
<div class="text-danger"><i class="far fa-frown"></i>No courses!</div>
{% endif %}
</div>
{% endif %} -->
{% if user.is_lecturer %}
<p class="form-title"><i class="fas fa-book-open"></i>My Courses</p>
<div class="dashboard-description">
{% if courses %}
<div class="flex">
{% for course in courses %}
<div class="flex"><a class="edit-btn" href="{{ course.get_absolute_url }}">{{ course }}</a></div>
{% endfor %}
</div>
{% else %}
<div class="text-danger"><i class="far fa-frown"></i>No courses!</div>
{% endif %}
</div>
{% endif %}
<p class="title-info"><i class="fas fa-user"></i> Personal Info</p>
<div class="dashboard-description">
<p><strong>First Name:</strong> {{ user.first_name|title }}</p>
<p><strong>Last Name:</strong> {{ user.last_name|title }}</p>
<p><strong>ID No.:</strong> {{ user.username }}</p>
</div>
{% if user.is_student %}
<p class="title-info"><i class="fas fa-graduation-cap"></i> Applicant Info</p>
<div class="dashboard-description">
<p><strong>School: </strong>Hawas Preparatory School</p>
<p><strong>Level: </strong>{{ level.level }}</p>
</div>
{% endif %}
<p class="title-info"><i class="fas fa-phone-square-alt"></i> Contact Info</p>
<div class="dashboard-description">
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Tel No.:</strong> {{ user.phone }}</p>
<p><strong>Address/city:</strong> {{ user.address }}</p>
</div>
<p class="title-info"><i class="fa fa-calendar-day"></i> Important Dates</p>
<div class="dashboard-description">
<p><strong>Last login:</strong> {{ user.last_login }}</p>
<p><strong>Academic Year:</strong> {{ current_semester }} Semester {{ current_session }}</p>
<p><strong>Registered Date:</strong> {{ user.date_joined|date }}</p>
</div>
</div>
</div>
<div class="col-md-3 mx-auto">
<div class="card">
{% if user.is_superuser %}
<p class="aside-text m-0">
<small>Message - </small> You can manage everything in the system but your normal job is only the list in the <a href="{% url 'admin_panel' %}">admin panel</a>, please do not do anything that is not listed in the admin panel unless it is very important.
<!-- You can control everything in the system but your normal work is only in the <a href="{% url 'admin_panel' %}">Admin panel</a>, please do not do anything that is not listed in the <a href="{% url 'admin_panel' %}">Admin panel</a> unless it is very important. -->
</p>
{% endif %}
<!-- {% if user.is_student %}
<p class="aside-text m-0"><small>{{ parent.user.get_full_name }} {% if parent.relation_ship != "Other" %}(your {{ parent.relation_ship }}){% endif %}{% trans " can see your attendace, assesment, and grade result" %}</small></p>
{% endif %} -->
</div>
</div>
</div>
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,127 @@
{% extends 'base.html' %}
{% block title %} {{ title }} | DjangoSMS{% endblock title %}
{% load static %}
{% load i18n %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> {{ user.get_full_name }}</div>
{% 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 %}
{% if request.user.is_superuser %}
<div class="btn-flex">
{% if user.is_student %}
<a class="edit-btn" href="{% url 'student_edit' pk=user.id %}">
<i class="fas fa-user-edit"></i><span class="mobile-hide">Edit Profile</span>
</a>
{% endif %}
{% if user.is_lecturer %}
<a class="edit-btn" href="{% url 'staff_edit' pk=user.id %}">
<i class="fas fa-user-edit"></i><span class="mobile-hide">Edit Profile</span>
</a>
{% endif %}
<!-- <a class="edit-btn" href="{% url 'change_password' %}"><i class="fas fa-lock"></i><span class="mobile-hide">
Change password</span>
</a> -->
</div>
{% endif %}
<div class="row justify-content-between">
<div class="col-md-3 mx-auto">
<div class="card">
<img src="{{ user.get_picture }}" class="dashboard-img">
<div class="program-description">
<p><strong>Last login: </strong>{{ user.last_login|date }}</p>
<p><strong>Full Name: </strong>{{ user.get_full_name|title }}</p>
<p><strong>Role: </strong>{{ user_type }}
</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
{% if user.is_lecturer %}
<p class="form-title"><i class="fas fa-book-open"></i>My Courses</p>
<div class="dashboard-description">
{% if courses %}
<div class="flex">
{% for course in courses %}
<div class="flex"><a class="edit-btn" href="{{ course.get_absolute_url }}">{{ course }}</a></div>
{% endfor %}
</div>
{% else %}
<div class="text-danger"><i class="far fa-frown"></i>No courses!</div>
{% endif %}
</div>
{% endif %}
<p class="title-info"><i class="fas fa-user"></i>Personal Info</p>
<div class="dashboard-description">
<p><strong>First Name:</strong> {{ user.first_name|title }}</p>
<p><strong>Last Name:</strong> {{ user.last_name|title }}</p>
<p><strong>ID No.:</strong> {{ user.username }}</p>
</div>
{% if user.is_student %}
<p class="title-info"><i class="fas fa-graduation-cap"></i>Applicant Info</p>
<div class="dashboard-description">
<p><strong>School: </strong>{{ student.get_student_school }}</p>
<p><strong>Department: </strong>{{ student.department }}</p>
<p><strong>Level: </strong>{{ student.level }}</p>
</div>
{% endif %}
<p class="title-info"><i class="fas fa-phone-square-alt"></i>Contact Info</p>
<div class="dashboard-description">
<p><strong>Email:</strong> {{ user.email }}</p>
<p><strong>Tel No.:</strong> {{ user.phone }}</p>
<p><strong>Address/city:</strong> {{ user.address }}</p>
</div>
<p class="title-info"><i class="fa fa-calendar-day"></i>Important Dates</p>
<div class="dashboard-description">
<p><strong>Last login:</strong> {{ user.last_login }}</p>
<p><strong>Academic Year:</strong> {{ current_semester }} Semester {{ current_session }}</p>
<p><strong>Registered Date:</strong> {{ user.date_joined|date }}</p>
</div>
</div>
</div>
<div class="col-md-3 mx-auto">
<div class="card">
{% if user.is_superuser %}
<p class="form-title m-0"><small>
You can control everything in the system but your normal work is only in the admin panel, please do not do anything that is not listed in the admin panel unless it is very important.</small>
</p>
{% endif %}
{% if user.is_student %}
<p class="form-title m-0">
<small class="text-warning">
<!-- You pass this semester with warning, so try to upgrade your assessments and get a good grade for the next time. -->
<!-- {{ parent.user.get_full_name }}
{% if parent.relation_ship != "Other" %}
(your {{ parent.relation_ship }})
{% endif %}
{% trans " can see your attendace, assesment, and grade result" %} -->
</small>
</p>
{% endif %}
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,110 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Students</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'add_student' %}"><i class="fas fa-plus"></i>Add Student</a>
{% endif %}
<div class="title-1"><i class="fas fa-user-graduate"></i>Students List</div>
<div class="title-line"></div>
{% 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 %}
<div class="content-center">
<form class="search-form" action="" method="GET">
<input class="au-input" type="text" id="student_id" name="student_id" placeholder="ID No." value="{{ request.GET.student_id }}"/>
<input class="au-input" type="text" name="name" placeholder="Name" value="{{ request.GET.name }}"/>
<input class="au-input" type="text" name="department" placeholder="Department" value="{{ request.GET.department }}"/>
<button class="btn btn-light" type="submit">
<i class="fas fa-search"></i> filter
</button>
</form>
</div>
<!-- <form class="search-form">
<ul>
<li class="space-between"><input class="form-control" type="text" name="name" placeholder="Name"></li>
<li class="space-between"><input class="form-control" type="text" name="email" placeholder="Email"></li>
<li class="space-between"><input class="form-control" type="text" name="course" placeholder="Course"></li>
<button class="search-btn" type="submit">
<i class="fas fa-search"></i>
</button>
</ul>
</form> -->
<div class="table-responsive table-shadow p-0 mt-5">
<table class="table">
<thead>
<tr>
<th>#</th>
<th> ID No. </th>
<th> Full Name </th>
<th> Email </th>
<th> Department </th>
{% if request.user.is_superuser %}
<th> Action </th>
{% endif %}
</tr>
</thead>
<tbody>
{% for student in object_list %}
<tr>
<td> {{ forloop.counter }}.</td>
<td>{{ student.student.username }} </td>
<td><a href="{% url 'profile_single' student.student.id %}">{{ student.student.get_full_name }}</a></td>
<td>{{ student.student.email }} </td>
<td>{{ student.department }}</td>
{% if request.user.is_superuser %}
<td>
<div class="update-delete">
<a href="{% url 'student_edit' student.student.pk %}" class="update" title="Edit"><i class="fas fa-edit"></i></a>
<form action="{% url 'student_delete' student.pk %}">{% csrf_token %}
<button type="submit" class="delete" title="Delete"><i class="fas fa-trash-alt"></i></button>
</form>
</div>
</td>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Student.
{% if request.user.is_superuser %}
<a href="{% url 'add_student' %}">
<i class="primary" style="font-size: 22px;">
Add Student Now.
</i>
{% endif %}
</a>
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

View File

@ -0,0 +1,86 @@
{% extends 'registration/registration_base.html' %}
{% block title %}DjangoSMS - Login{% endblock title %}
{% load crispy_forms_tags %}
{% block content %}
<div id="login">
<div class="login-title blue-gradient"><i class="fas fa-lock"></i>Sign in</div>
<form action="" method="POST" id="login-form">{% csrf_token %}
<!-- {{ form|crispy }} -->
<div class="form-group px-3">
<label for="username"><i class="fas fa-address-card"></i>ID Number</label>
<!-- {{ form.username }} -->
<input type="text" name="username" id="username" class="form-control" required>
</div>
<div class="form-group px-3">
<label for="password"><i class="fas fa-key"></i>Password</label>
<!-- {{ form.password }} -->
<input type="password" name="password" id="password" class="form-control" required>
</div>
{% if form.errors %}
<span class="text-danger"><i class="fas fa-exclamation-circle"></i> Invalid ID & Password.</span><br>
{% endif %}
<button type="submit" class="btn btn-primary" id="login-btn"><i class="fas fa-sign-in-alt"></i><small> SIGN IN</small></button>
</form>
<br>
<a href="{% url 'password_reset' %}" class="link">Forgot password ?</a>
</div>
<script>
const loginFormEl = document.getElementById('login-form');
const loginBtnEl = document.getElementById('login-btn');
// const method = 'POST';
loginFormEl.addEventListener('submit', ()=>{
// console.log(loginBtnEl);
loginBtnEl.innerHTML = '<i class="fas fa-sign-in-alt"></i> Signining you in . . .';
loginBtnEl.classList.add("disabled");
})
// function replaceHTML(){
// // loginBtnEl.classList.add('disabled');
// // loginBtnEl.style.cursor = 'not-allowed';
// loginBtnEl.setAttribute("disabled")
// console.log(loginBtnEl)
// loginBtnEl.innerHTML = '<i class="fas fa-sign-in-alt"></i> Signining you in . . .';
// }
// loginBtnEl.addEventListener('click', ()=>{
// loginBtnEl.classList.add('disabled');
// loginBtnEl.style.cursor = 'not-allowed';
// loginBtnEl.innerHTML = '<i class="fas fa-sign-in-alt"></i> Signining you in &point . . .';
// })
// console.log(FormData)
// const xhr = new XMLHttpRequest()
// console.log(xhr)
// console.log(xhr.response)
// console.log(xhr.response.username)
// console.log(xhr.response.password)
// if (xhr.method == 'POST'){
// console.log(xhr.response)
// console.log("Hey there")
// function replaceHTML(){
// loginBtnEl.classList.add('disabled');
// loginBtnEl.style.cursor = 'not-allowed';
// loginBtnEl.innerHTML = '<i class="fas fa-sign-in-alt"></i> Signining you in . . .';
// }
// }
// xhr.onload = function replaceHTML(){
// loginBtnEl.addEventListener('click', ()=>{
// loginBtnEl.classList.add('disabled');
// loginBtnEl.style.cursor = 'not-allowed';
// loginBtnEl.innerHTML = '<i class="fas fa-sign-in-alt"></i> Signining you in . . .';
// })
// }
// xhr.send()
// const method = 'GET'
// const url = "/accounts/login/"
// const responseType = "json"
// xhr.responseType = responseType
// xhr.open(method, url)
// xhr.onload = function() {
// console.log(xhr.response)
// }
// xhr.send()
</script>
{% endblock content %}

View File

@ -0,0 +1,19 @@
{% extends 'registration/registration_base.html' %}
{% block title %}Password Reset | DjangoSMS{% endblock title %}
{% load crispy_forms_tags %}
{% block content %}
<div id="login">
<h3 class="login-title">Password Reset</h3>
<form action="" method="POST" class="form-box">{% csrf_token %}
<div class="container">
<!-- {{ form|crispy }} -->
<div class="form-group">
<i class="fas fa-envelope"></i>Email {{ form.email }}
<span class="danger">{{ form.email.errors }}</span>
</div>
</div>
<button type="submit" class="btn btn-primary"><small>Request Password Reset</small></button>
</form>
</div>
{% endblock content %}

View File

@ -0,0 +1,15 @@
{% extends 'registration/registration_base.html' %}
{% block title %}Password Reset Complete | DjangoSMS{% endblock title %}
{% load crispy_forms_tags %}
{% block content %}
<div id="login">
<h3 class="login-title">Password Reset Complete</h3>
<div class="container">
<div class="alert alert-success">
<i class="fas fa-check-circle"></i>Your password has been set, you are now able to Log In!
</div>
<a class="btn btn-primary" href="{% url 'login' %}">Sign In Here</a>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,34 @@
{% extends 'registration/registration_base.html' %}
{% block title %}New Password | DjangoSMS{% endblock title %}
{% load crispy_forms_tags %}
{% block content %}
{% 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 %}
<div id="login">
<h3 class="login-title">Confirm New Password</h3>
<div class="p-3">
<form action="" method="POST" class="form-box text-left">{% csrf_token %}
{{ form|crispy }}
<!-- <label for="password1">New Password</label>
<input type="password" id="password1" name="password1" class="form-control" required><hr>
<label for="password2">Confirm Password</label>
<input type="password" id="password2" name="password2" class="form-control" required>
<hr> -->
<input type="submit" class="btn btn-primary" value="Reset Password">
</form>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,16 @@
{% extends 'registration/registration_base.html' %}
{% block title %}Email Sent | DjangoSMS{% endblock title %}
{% load crispy_forms_tags %}
{% block content %}
<div id="login">
<h3 class="login-title">Email sent</h3>
<div class="container">
<div class="alert alert-success">
<i class="fas fa-check-circle"></i>An Email has been sent with instructions
to reset your password, check your email.
</div>
<a class="btn btn-primary" href="{% url 'login' %}"><i class="far fa-arrow-alt-circle-left"></i>Back To Login</a>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,24 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{% block title %}DjangoSMS - Login{% endblock title %}</title>
<link href="{% static 'css/font-face.css' %}" rel="stylesheet" media="all">
<link rel="stylesheet" type="text/css" href="{% static 'css/all.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
</head>
<body style="background: #fff;">
<div class="container">
{% block content %}
{% endblock content %}
</div>
</body>
</html>

View File

@ -0,0 +1,62 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Admin Panel</div>
<!-- <br> -->
<div class="title-1"><i class="fas fa-user-tie"></i>Admin Panel</div>
<div class="title-line mb-3"></div>
<p class="text-center text-muted mb-5">List of actions that only the admin can access</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 %}
<div class="admin-panel">
<div class="row">
<div class="col-md-6 p-5 bg-gradient-light">
Manage<a class="btn btn-sm btn-secondary" href="{% url 'lecturer_list' %}"> Lecturers &raquo;</a>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) lecturers</p><hr>
Manage<a class="btn btn-sm btn-secondary" href="{% url 'student_list' %}"> Students &raquo;</a>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) students</p><hr>
Manage<a class="btn btn-sm btn-secondary" href="{% url 'session_list' %}"> Session &raquo;</a>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) sessions</p><hr>
Manage<a class="btn btn-sm btn-secondary" href="{% url 'semester_list' %}"> Semester &raquo;</a>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) semesters</p>
</div>
<div class="col-md-6 p-5 bg-gradient-light">
Course Add & Drop
<label class="switch switch-text switch-success switch-pill" style="float: right;">
<input type="checkbox" class="switch-input" checked="true">
<span data-on="On" data-off="Off" class="switch-label"></span>
<span class="switch-handle"></span>
</label>
<p><span>&LongRightArrow;</span> Switch
<i class="info-text bg-success">ON</i> or <i class="info-text bg-danger">OFF</i>
</p><hr>
Manage<a class="btn btn-sm btn-secondary" href="{% url 'programs' %}"> Programs & Courses &raquo;</a>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) programs</p>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) courses</p><hr>
Manage<a class="btn btn-sm btn-secondary" href="{% url 'course_allocation_view' %}"> Course Allocations &raquo;</a>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) course allocations</p><hr>
Manage<a class="btn btn-sm btn-secondary" href="{% url 'home' %}"> News & Events &raquo;</a>
<p><span>&LongRightArrow;</span> CRUD (Create, Retriev, Update & Delete) News & Events</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Password Change</div>
<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 %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card">
<p class="form-title"><i class="fas fa-lock"></i>Change Password</p><br>
<div class="container">
<!-- {{ form|crispy }} -->
<form action="" method="POST">{% csrf_token %}
<div class="form-group">
<label>{{ form.old_password.label }}</label>{{ form.old_password }}
<span class="danger">{{ form.old_password.errors }}</span>
<p class="text-muted-xs">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</p>
</div>
<div class="form-group">
<label>{{ form.new_password1.label }}</label>{{ form.new_password1 }}
<span class="danger">{{ form.new_password1.errors }}</span>
<p class="text-muted-xs">
Your password can't be too similar to your other personal information.<br>
Your password must contain at least 8 characters.<br>
Your password can't be a commonly used password.<br>
Your password can't be entirely numeric.
</p>
</div>
<div class="form-group">
<label>{{ form.new_password2.label }}</label>{{ form.new_password2 }}
<span class="danger">{{ form.new_password2.errors }}</span>
<p class="text-muted-xs">Enter the same password as before, for verification.</p>
</div>
<center><input class="btn btn-outline-primary" type="submit" value="Change Password"></center><br>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,72 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Account setting</div>
<p class="title-1"><i class="fas fa-cogs"></i>Account Settings</p>
<div class="title-line"></div>
{% 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 %}
<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="form-title">Email & Personal Info</div>
<div class="container">
<div class="form-group">
<label>{{ form.email.label }}</label>{{ form.email }}
<span class="danger">{{ form.email.errors }}</span>
</div>
<div class="form-group">
<label>{{ form.first_name.label }}</label>{{ form.first_name }}
<span class="danger">{{ form.first_name.errors }}</span>
</div>
<div class="form-group">
<label>{{ form.last_name.label }}</label>{{ form.last_name }}
<span class="danger">{{ form.last_name.errors }}</span>
</div>
<div class="form-group">
<label>{{ form.phone.label }}</label>{{ form.phone }}
<span class="danger">{{ form.phone.errors }}</span>
</div>
<div class="form-group">
<label>{{ form.address.label }}</label>{{ form.address }}
<span class="danger">{{ form.address.errors }}</span>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<p class="form-title">Others</p>
<div class="container">
<div class="form-group"><label>Profile Picture</label><br>
{{ form.picture }}
<span class="danger">{{ form.picture.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<center>
<button class="btn btn-outline-primary" type="submit">Save</button>
</center>
</form>
{% endblock content %}

3
accounts/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

68
accounts/urls.py Normal file
View File

@ -0,0 +1,68 @@
from django.conf.urls import url
from django.urls import path
from django.contrib.auth.views import (
PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView,
PasswordResetCompleteView, LoginView, LogoutView
)
from .views import (
profile, profile_single, admin_panel,
profile_update, change_password,
LecturerListView, StudentListView,
staff_add_view, edit_staff,
delete_staff, student_add_view,
edit_student, delete_student, ParentAdd
)
from .forms import EmailValidationOnForgotPassword
urlpatterns = [
url(r'^admin_panel/$', admin_panel, name='admin_panel'),
url(r'^profile/$', profile, name='profile'),
url(r'^profile/(?P<id>\d+)/detail/$', profile_single, name='profile_single'),
url(r'^setting/$', profile_update, name='edit_profile'),
url(r'^change_password/$', change_password, name='change_password'),
url(r'^lecturers/$', LecturerListView.as_view(), name='lecturer_list'),
url(r'^lecturer/add/$', staff_add_view, name='add_lecturer'),
url(r'^staff/(?P<pk>\d+)/edit/$', edit_staff, name='staff_edit'),
url(r'^lecturers/(?P<pk>\d+)/delete/$', delete_staff, name='lecturer_delete'),
url(r'^students/$', StudentListView.as_view(), name='student_list'),
url(r'^student/add/$', student_add_view, name='add_student'),
url(r'^student/(?P<pk>\d+)/edit/$', edit_student, name='student_edit'),
url(r'^students/(?P<pk>\d+)/delete/$', delete_student, name='student_delete'),
url(r'^parents/add/$', ParentAdd.as_view(), name='add_parent'),
# url(r'^add-student/$', StudentAddView.as_view(), name='add_student'),
# url(r'^programs/course/delete/(?P<pk>\d+)/$', course_delete, name='delete_course'),
# Setting urls
# url(r'^profile/(?P<pk>\d+)/edit/$', profileUpdateView, name='edit_profile'),
# url(r'^profile/(?P<pk>\d+)/change-password/$', changePasswordView, name='change_password'),
# ################################################################
# url(r'^login/$', LoginView.as_view(), name='login'),
# url(r'^logout/$', LogoutView.as_view(), name='logout', kwargs={'next_page': '/'}),
# url(r'^password-reset/$', PasswordResetView.as_view(
# form_class=EmailValidationOnForgotPassword,
# template_name='registration/password_reset.html'
# ),
# name='password_reset'),
# url(r'^password-reset/done/$', PasswordResetDoneView.as_view(
# template_name='registration/password_reset_done.html'
# ),
# name='password_reset_done'),
# url(r'^password-reset-confirm/<uidb64>/<token>/$', PasswordResetConfirmView.as_view(
# template_name='registration/password_reset_confirm.html'
# ),
# name='password_reset_confirm'),
# url(r'^password-reset-complete/$', PasswordResetCompleteView.as_view(
# template_name='registration/password_reset_complete.html'
# ),
# name='password_reset_complete')
# ################################################################
]

15
accounts/validators.py Normal file
View File

@ -0,0 +1,15 @@
import re
from django.core import validators
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
@deconstructible
class ASCIIUsernameValidator(validators.RegexValidator):
regex = r'^[a-zA-Z]+\/(...)\/(....)'
message = _(
'Enter a valid username. This value may contain only English letters, '
'numbers, and @/./+/-/_ characters.'
)
flags = re.ASCII

320
accounts/views.py Normal file
View File

@ -0,0 +1,320 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.http import Http404
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth import update_session_auth_hash
from django.views.generic import CreateView, ListView
from django.core.paginator import Paginator
from django.db.models import Q
from django.utils.decorators import method_decorator
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordChangeForm
from .decorators import lecturer_required, student_required, admin_required
from course.models import Course
from result.models import TakenCourse
from app.models import Session, Semester
from .forms import StaffAddForm, StudentAddForm, ProfileUpdateForm, ParentAddForm
from .models import User, Student, Parent
@login_required
def profile(request):
""" Show profile of any user that fire out the request """
try:
current_session = get_object_or_404(Session, is_current_session=True)
current_semester = get_object_or_404(Semester, is_current_semester=True, session=current_session)
except Semester.MultipleObjectsReturned and Semester.DoesNotExist and Session.DoesNotExist:
raise Http404
if request.user.is_lecturer:
courses = Course.objects.filter(allocated_course__lecturer__pk=request.user.id).filter(
semester=current_semester)
return render(request, 'accounts/profile.html', {
'title': request.user.get_full_name,
"courses": courses,
'current_session': current_session,
'current_semester': current_semester,
})
elif request.user.is_student:
level = Student.objects.get(student__pk=request.user.id)
try:
parent = Parent.objects.get(student=level)
except:
parent = "no parent set"
courses = TakenCourse.objects.filter(student__student__id=request.user.id, course__level=level.level)
context = {
'title': request.user.get_full_name,
'parent': parent,
'courses': courses,
'level': level,
'current_session': current_session,
'current_semester': current_semester,
}
return render(request, 'accounts/profile.html', context)
else:
staff = User.objects.filter(is_lecturer=True)
return render(request, 'accounts/profile.html', {
'title': request.user.get_full_name,
"staff": staff,
'current_session': current_session,
'current_semester': current_semester,
})
@login_required
@admin_required
def profile_single(request, id):
""" Show profile of any selected user """
if request.user.id == id:
return redirect("/profile/")
current_session = get_object_or_404(Session, is_current_session=True)
current_semester = get_object_or_404(Semester, is_current_semester=True, session=current_session)
user = User.objects.get(pk=id)
if user.is_lecturer:
courses = Course.objects.filter(allocated_course__lecturer__pk=id).filter(semester=current_semester)
context = {
'title': user.get_full_name,
"user": user,
"user_type": "Lecturer",
"courses": courses,
'current_session': current_session,
'current_semester': current_semester,
}
return render(request, 'accounts/profile_single.html', context)
elif user.is_student:
student = Student.objects.get(student__pk=id)
courses = TakenCourse.objects.filter(student__student__id=id, course__level=student.level)
context = {
'title': user.get_full_name,
'user': user,
"user_type": "student",
'courses': courses,
'student': student,
'current_session': current_session,
'current_semester': current_semester,
}
return render(request, 'accounts/profile_single.html', context)
else:
context = {
'title': user.get_full_name,
"user": user,
"user_type": "superuser",
'current_session': current_session,
'current_semester': current_semester,
}
return render(request, 'accounts/profile_single.html', context)
@login_required
@admin_required
def admin_panel(request):
return render(request, 'setting/admin_panel.html', {})
# ########################################################
# ########################################################
# Setting views
# ########################################################
@login_required
def profile_update(request):
if request.method == 'POST':
form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user)
if form.is_valid():
form.save()
messages.success(request, 'Your profile has been updated successfully.')
return redirect('profile')
else:
messages.error(request, 'Please correct the error(s) below.')
else:
form = ProfileUpdateForm(instance=request.user)
return render(request, 'setting/profile_info_change.html', {
'title': 'Setting | DjangoSMS',
'form': form,
})
@login_required
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(request.user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
messages.success(request, 'Your password was successfully updated!')
return redirect('profile')
else:
messages.error(request, 'Please correct the error(s) below. ')
else:
form = PasswordChangeForm(request.user)
return render(request, 'setting/password_change.html', {
'form': form,
})
# ########################################################
@login_required
@admin_required
def staff_add_view(request):
if request.method == 'POST':
form = StaffAddForm(request.POST)
first_name = request.POST.get('first_name')
last_name = request.POST.get('last_name')
if form.is_valid():
form.save()
messages.success(request, "Account for lecturer " + first_name + ' ' + last_name + " has been created.")
return redirect("lecturer_list")
else:
form = StaffAddForm()
context = {
'title': 'Lecturer Add | DjangoSMS',
'form': form,
}
return render(request, 'accounts/add_staff.html', context)
@login_required
@admin_required
def edit_staff(request, pk):
instance = get_object_or_404(User, is_lecturer=True, pk=pk)
if request.method == 'POST':
form = ProfileUpdateForm(request.POST, request.FILES, instance=instance)
full_name = instance.get_full_name
if form.is_valid():
form.save()
messages.success(request, 'Lecturer ' + full_name + ' has been updated.')
return redirect('lecturer_list')
else:
messages.error(request, 'Please correct the error below.')
else:
form = ProfileUpdateForm(instance=instance)
return render(request, 'accounts/edit_lecturer.html', {
'title': 'Edit Lecturer | DjangoSMS',
'form': form,
})
@method_decorator([login_required, admin_required], name='dispatch')
class LecturerListView(ListView):
queryset = User.objects.filter(is_lecturer=True)
template_name = "accounts/lecturer_list.html"
paginate_by = 10 # if pagination is desired
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Lecturers | DjangoSMS"
return context
# @login_required
# @lecturer_required
# def delete_staff(request, pk):
# staff = get_object_or_404(User, pk=pk)
# staff.delete()
# return redirect('lecturer_list')
@login_required
@admin_required
def delete_staff(request, pk):
lecturer = get_object_or_404(User, pk=pk)
full_name = lecturer.get_full_name
lecturer.delete()
messages.success(request, 'Lecturer ' + full_name + ' has been deleted.')
return redirect('lecturer_list')
# ########################################################
# ########################################################
# Student views
# ########################################################
@login_required
@admin_required
def student_add_view(request):
if request.method == 'POST':
form = StudentAddForm(request.POST)
first_name = request.POST.get('first_name')
last_name = request.POST.get('last_name')
if form.is_valid():
form.save()
messages.success(request, 'Account for ' + first_name + ' ' + last_name + ' has been created.')
return redirect('student_list')
else:
messages.error(request, 'Correct the error(s) below.')
else:
form = StudentAddForm()
return render(request, 'accounts/add_student.html', {
'title': "Add Student | DjangoSMS",
'form': form
})
@login_required
@admin_required
def edit_student(request, pk):
# instance = User.objects.get(pk=pk)
instance = get_object_or_404(User, is_student=True, pk=pk)
if request.method == 'POST':
form = ProfileUpdateForm(request.POST, request.FILES, instance=instance)
full_name = instance.get_full_name
if form.is_valid():
form.save()
messages.success(request, ('Student ' + full_name + ' has been updated.'))
return redirect('student_list')
else:
messages.error(request, 'Please correct the error below.')
else:
form = ProfileUpdateForm(instance=instance)
return render(request, 'accounts/edit_student.html', {
'title': 'Edit-profile | DjangoSMS',
'form': form,
})
@method_decorator([login_required, admin_required], name='dispatch')
class StudentListView(ListView):
template_name = "accounts/student_list.html"
paginate_by = 10 # if pagination is desired
def get_queryset(self):
queryset = Student.objects.all()
query = self.request.GET.get('student_id')
if query is not None:
queryset = queryset.filter(Q(department=query))
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Students | DjangoSMS"
return context
@login_required
@admin_required
def delete_student(request, pk):
student = get_object_or_404(Student, pk=pk)
# full_name = student.user.get_full_name
student.delete()
messages.success(request, 'Student has been deleted.')
return redirect('student_list')
# ########################################################
class ParentAdd(CreateView):
model = Parent
form_class = ParentAddForm
template_name = 'accounts/parent_form.html'
# def parent_add(request):
# if request.method == 'POST':
# form = ParentAddForm(request.POST)
# if form.is_valid():
# form.save()
# return redirect('student_list')
# else:
# form = ParentAddForm(request.POST)

0
app/__init__.py Normal file
View File

9
app/admin.py Normal file
View File

@ -0,0 +1,9 @@
from django.contrib import admin
from django.contrib.auth.models import Group
from .models import Session, Semester, NewsAndEvents
admin.site.register(Semester)
admin.site.register(Session)
admin.site.register(NewsAndEvents)

5
app/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class AppConfig(AppConfig):
name = 'app'

74
app/forms.py Normal file
View File

@ -0,0 +1,74 @@
from django import forms
from django.db import transaction
from .models import NewsAndEvents, Session, Semester, SEMESTER
# news and events
class NewsAndEventsForm(forms.ModelForm):
class Meta:
model = NewsAndEvents
fields = ('title', 'summary', 'posted_as',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'class': 'form-control'})
self.fields['summary'].widget.attrs.update({'class': 'form-control'})
self.fields['posted_as'].widget.attrs.update({'class': 'form-control'})
class SessionForm(forms.ModelForm):
next_session_begins = forms.DateTimeField(
widget=forms.TextInput(
attrs={
'type': 'date',
}
),
required=True)
class Meta:
model = Session
fields = ['session', 'is_current_session', 'next_session_begins']
class SemesterForm(forms.ModelForm):
semester = forms.CharField(
widget=forms.Select(
choices=SEMESTER,
attrs={
'class': 'browser-default custom-select',
}
),
label="semester",
)
is_current_semester = forms.CharField(
widget=forms.Select(
choices=((True, 'Yes'), (False, 'No')),
attrs={
'class': 'browser-default custom-select',
}
),
label="is current semester ?",
)
session = forms.ModelChoiceField(
queryset=Session.objects.all(),
widget=forms.Select(
attrs={
'class': 'browser-default custom-select',
}
),
required=True
)
next_semester_begins = forms.DateTimeField(
widget=forms.TextInput(
attrs={
'type': 'date',
'class': 'form-control',
}
),
required=True)
class Meta:
model = Semester
fields = ['semester', 'is_current_semester', 'session', 'next_semester_begins']

View File

@ -0,0 +1,45 @@
# Generated by Django 2.2.3 on 2020-07-29 15:25
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='NewsAndEvents',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200, null=True)),
('summary', models.TextField(blank=True, max_length=200, null=True)),
('posted_as', models.CharField(choices=[('News', 'News'), ('Event', 'Event')], max_length=10)),
('updated_date', models.DateTimeField(auto_now=True, null=True)),
('upload_time', models.DateTimeField(auto_now_add=True, null=True)),
],
),
migrations.CreateModel(
name='Session',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('session', models.CharField(max_length=200, unique=True)),
('is_current_session', models.BooleanField(blank=True, default=False, null=True)),
('next_session_begins', models.DateField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Semester',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('semester', models.CharField(blank=True, choices=[('First', 'First'), ('Second', 'Second'), ('Third', 'Third')], max_length=10)),
('is_current_semester', models.BooleanField(blank=True, default=False, null=True)),
('next_semester_begins', models.DateField(blank=True, null=True)),
('session', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='app.Session')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2020-07-30 04:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='semester',
name='semester',
field=models.CharField(blank=True, choices=[('First', 'First'), ('Second', 'Second'), ('Third', 'Third')], max_length=10, unique=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2020-07-30 04:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0002_auto_20200730_0746'),
]
operations = [
migrations.AlterField(
model_name='semester',
name='semester',
field=models.CharField(blank=True, choices=[('First', 'First'), ('Second', 'Second'), ('Third', 'Third')], max_length=10),
),
]

View File

82
app/models.py Normal file
View File

@ -0,0 +1,82 @@
from django.db import models
from django.urls import reverse
from django.core.validators import FileExtensionValidator
from django.contrib.auth.models import AbstractUser
from django.db.models import Q
NEWS = "News"
EVENTS = "Event"
POST = (
(NEWS, "News"),
(EVENTS, "Event"),
)
FIRST = "First"
SECOND = "Second"
THIRD = "Third"
SEMESTER = (
(FIRST, "First"),
(SECOND, "Second"),
(THIRD, "Third"),
)
class NewsAndEventsQuerySet(models.query.QuerySet):
def search(self, query):
lookups = (Q(title__icontains=query) |
Q(summary__icontains=query) |
Q(posted_as__icontains=query)
)
return self.filter(lookups).distinct()
class NewsAndEventsManager(models.Manager):
def get_queryset(self):
return NewsAndEventsQuerySet(self.model, using=self._db)
def all(self):
return self.get_queryset()
def get_by_id(self, id):
qs = self.get_queryset().filter(id=id) # NewsAndEvents.objects == self.get_queryset()
if qs.count() == 1:
return qs.first()
return None
def search(self, query):
return self.get_queryset().search(query)
class NewsAndEvents(models.Model):
title = models.CharField(max_length=200, null=True)
summary = models.TextField(max_length=200, blank=True, null=True)
posted_as = models.CharField(choices=POST, max_length=10)
updated_date = models.DateTimeField(auto_now=True, auto_now_add=False, null=True)
upload_time = models.DateTimeField(auto_now=False, auto_now_add=True, null=True)
objects = NewsAndEventsManager()
def __str__(self):
return self.title
class Session(models.Model):
session = models.CharField(max_length=200, unique=True)
is_current_session = models.BooleanField(default=False, blank=True, null=True)
next_session_begins = models.DateField(blank=True, null=True)
def __str__(self):
return self.session
class Semester(models.Model):
semester = models.CharField(max_length=10, choices=SEMESTER, blank=True)
is_current_semester = models.BooleanField(default=False, blank=True, null=True)
session = models.ForeignKey(Session, on_delete=models.CASCADE, blank=True, null=True)
next_semester_begins = models.DateField(null=True, blank=True)
def __str__(self):
return self.semester

View File

@ -0,0 +1,90 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load static %}
{% block content %}
<div id="input-nav" class="p-2">Home</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'add_item' %}"><i class="fas fa-plus"></i>Add New Post</a>
{% endif %}
{% 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 %}<br>
<div class="title-1">News & Events</div>
<div class="title-line mb-4"></div>
{% if items %}
<div class="row">
{% for item in items %}
<div class="col-md-4 mb-4 mx-auto">
<div class="card bs-md">
<div class="{% if item.posted_as == 'News' %}news{% else %}events{% endif %} pl-5 pr-2">{{ item.title|title }}</div>
<div class="news-events-wrapper">
<span class="info-text">{{ item.posted_as }}</span>
</div>
{% if request.user.is_superuser %}
<!-- <div class="update-delete-wrapper">
<div class="drop-down">
<a href="#"><i class="fas fa-ellipsis-v"></i></a>
<div class="content">
<div class="content-a">
<a href="{% url 'edit_post' pk=item.id %}" class="update" title="Edit"><i class="fas fa-pencil-alt"></i> Edit</a>
<a href="{% url 'delete_post' pk=item.id %}" class="delete" title="Delete"><i class="fas fa-trash-alt"></i> Delete</a>
</div>
</div>
</div>
</div> -->
<div class="update-delete-wrapper">
<div class="dropdown">
<button class="btn btn-sm " type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-ellipsis-v text-white"></i>
</button>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuButton">
<a class="dropdown-item" href="{% url 'edit_post' pk=item.id %}" class="update"><i class="fas fa-pencil-alt"></i> Edit</a>
<a class="dropdown-item" href="{% url 'delete_post' pk=item.id %}" class="delete"><i class="fas fa-trash-alt"></i> Delete</a>
<!-- <a class="dropdown-item" href="#">Something else here</a> -->
</div>
</div>
</div>
<!-- <div class="update-delete-wrapper">
<div class="navbar">
<li class="dropdown">
<a class="dropdown-toggle-split" href="#" id="dropdown01" data-toggle="dropdown"><i class="fas fa-ellipsis-v text-white"></i></a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown01">
<a href="{% url 'edit_post' pk=item.id %}" class="update"><i class="fas fa-pencil-alt"></i> Edit</a>
<a href="{% url 'delete_post' pk=item.id %}" class="delete"><i class="fas fa-trash-alt"></i> Delete</a>
</div>
</li>
</div>
</div> -->
{% endif %}
<div class="card-subtitle m-2">{{ item.summary }}</div><br><br><br>
<div class="date-wrapper"><i class="fa fa-calendar mr-2"></i>{{ item.updated_date|timesince }} ago</div>
{% if forloop.counter|divisibleby:3 %}</div></div></div><div class="row">
{% else %}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% else %}
<div class="col-md-12">
<div class="form-title"><i class="far fa-frown fa-2x"></i>No News & Events yet.</div>
</div>
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Add Item & Update</div>
<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 %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card pb-3">
<p class="form-title">Item Post & Update Form</p>
<div class="container"><br>
<form action="" method="POST">{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-primary" type="submit" value="POST">
<a class="btn btn-danger" href="{% url 'home' %}" style="float: right;">Cancel</a>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,91 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Semester list</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'add_semester' %}"><i class="fas fa-plus"></i>Add New Semester</a>
{% endif %}
<div class="title-1"><i class="fas fa-calendar-alt"></i>Semester List</div>
<div class="title-line"></div>
{% 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 %}
<div class="table-responsive table-shadow p-0 mt-5">
<table class="table">
<thead>
<tr>
<th>#</th>
<th> Semester </th>
<th> Is Current semester </th>
<th> Session </th>
<th> Next Semester Begins </th>
{% if request.user.is_superuser %}
<th> Actions </th>
{% endif %}
</tr>
</thead>
<tbody>
{% for semester in semesters %}
<tr>
<td> {{ forloop.counter }}.</td>
<td>{{ semester.semester }}</td>
<th>{% if semester.is_current_semester == False %}<i class="fas fa-times-circle fa-1-5x danger"></i>
{% else %}<i class="fas fa-check-circle fa-1-5x"></i>
<i class="icon-times-circle">
</i>{% endif %}
</th>
<td>{{ semester.session }}</td>
<td>{{ semester.next_semester_begins }}</td>
{% if request.user.is_superuser %}
<td> <div class="update-delete">
<a href="{% url 'edit_semester' pk=semester.pk %}" class="update" title="Edit"><i class="fas fa-pencil-alt"></i></a>
<a href="{% url 'delete_semester' pk=semester.pk %}" class="delete" title="Delete"><i class="fas fa-trash-alt"></i></a>
</div>
</td>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Semester.
{% if request.user.is_superuser %}
<a href="{% url 'add_semester' %}">
<i class="primary" style="font-size: 22px;">
Add Semester Now.
</i>
{% endif %}
</a>
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

View File

@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'semester_list' %}" class="primary1">Semester List</a> <i>&rsaquo;</i> Semester Add & update</div>
<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 %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card pb-3">
<p class="form-title">Semester Add & update Form</p>
<div class="container"><br>
<form action="" method="POST">{% csrf_token %}
{% for f in form %}{{ f.label }}{{ f }}<hr>{% endfor %}
<input class="btn btn-primary" type="submit" value="Save">
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,90 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Session list</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'add_session' %}"><i class="fas fa-plus"></i>Add New Session</a>
{% endif %}
<div class="title-1"><i class="fas fa-calendar-week"></i>Session List</div>
<div class="title-line"></div>
{% 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 %}
<div class="table-responsive table-shadow p-0 mt-5">
<table class="table">
<thead>
<tr>
<th>#</th>
<th> Session </th>
<th> Is Current Session </th>
<th> Next Session Begins </th>
{% if request.user.is_superuser %}
<th> Actions </th>
{% endif %}
</tr>
</thead>
<tbody>
{% for session in sessions %}
<tr>
<td>{{ forloop.counter }}.</td>
<td>{{ session.session }}</td>
<th>
{% if session.is_current_session == True %}
<i class="fas fa-check-circle fa-1-5x"></i>
{% else %}
<i class="fas fa-times-circle fa-1-5x danger"></i>
{% endif %}
</th>
<td>{{ session.next_session_begins }}</td>
{% if request.user.is_superuser %}
<td> <div class="update-delete">
<a href="{% url 'edit_session' pk=session.pk %}" class="update" title="Edit"><i class="fas fa-pencil-alt"></i></a>
<a href="{% url 'delete_session' pk=session.pk %}" class="delete" title="Delete"><i class="fas fa-trash-alt"></i></a>
</div>
</td>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Session.
{% if request.user.is_superuser %}
<a href="{% url 'add_session' %}">
<i class="primary" style="font-size: 22px;">
Add Session Now.
</i>
{% endif %}
</a>
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

View File

@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'session_list' %}" class="primary1">Session List</a> <i>&rsaquo;</i> Session Add & update</div>
<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 %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card pb-3">
<p class="form-title">Session Add & update Form</p>
<div class="container"><br>
<form action="" method="POST">{% csrf_token %}
{{ form|crispy }}
<input class="btn btn-primary" type="submit" value="Save">
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,10 @@
{% extends 'base.html' %}
{% block title %}Page Not Found 404 | CFE{% endblock title %}
{% block content %}
<br><br><br><br><br>
<div class="container text-center">
<div class="display-1 text-primary" style="font-size: 12rem;">404</div>
<div class="bg-light p-3 display-5 text-warning">Page Not Found</div>
</div>
{% endblock content %}

3
app/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

27
app/urls.py Normal file
View File

@ -0,0 +1,27 @@
from django.conf.urls import url
from django.urls import path
from .views import (
home_view, post_add, edit_post, delete_post,
session_list_view, session_add_view, session_update_view, session_delete_view,
semester_list_view, semester_add_view, semester_update_view, semester_delete_view
)
urlpatterns = [
# Accounts url
url(r'^$', home_view, name='home'),
url(r'^add_item/$', post_add, name='add_item'),
url(r'^item/(?P<pk>\d+)/edit/$', edit_post, name='edit_post'),
url(r'^item/(?P<pk>\d+)/delete/$', delete_post, name='delete_post'),
url(r'^session/$', session_list_view, name="session_list"),
url(r'^session/add/$', session_add_view, name="add_session"),
url(r'^session/(?P<pk>\d+)/edit/$', session_update_view, name="edit_session"),
url(r'^session/(?P<pk>\d+)/delete/$', session_delete_view, name="delete_session"),
url(r'^semester/$', semester_list_view, name="semester_list"),
url(r'^semester/add/$', semester_add_view, name="add_semester"),
url(r'^semester/(?P<pk>\d+)/edit/$', semester_update_view, name="edit_semester"),
url(r'^semester/(?P<pk>\d+)/delete/$', semester_delete_view, name="delete_semester"),
]

292
app/views.py Normal file
View File

@ -0,0 +1,292 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.contrib.auth import update_session_auth_hash
from django.views.generic import CreateView, ListView
from django.core.paginator import Paginator
from django.conf import settings
from django.utils.decorators import method_decorator
from accounts.decorators import lecturer_required, student_required
from .forms import SessionForm, SemesterForm, NewsAndEventsForm
from .models import *
User = settings.AUTH_USER_MODEL
# ########################################################
# News & Events
# ########################################################
@login_required
def home_view(request):
items = NewsAndEvents.objects.all().order_by('-updated_date')
context = {
'title': "News & Events | DjangoSMS",
'items': items,
}
return render(request, 'app/index.html', context)
@login_required
def post_add(request):
if request.method == 'POST':
form = NewsAndEventsForm(request.POST)
title = request.POST.get('title')
if form.is_valid():
form.save()
messages.success(request, (title + ' has been uploaded.'))
return redirect('home')
else:
messages.error(request, 'Please correct the error(s) below.')
else:
form = NewsAndEventsForm()
return render(request, 'app/post_add.html', {
'title': 'Add Post | DjangoSMS',
'form': form,
})
@login_required
@lecturer_required
def edit_post(request, pk):
instance = get_object_or_404(NewsAndEvents, pk=pk)
if request.method == 'POST':
form = NewsAndEventsForm(request.POST, instance=instance)
title = request.POST.get('title')
if form.is_valid():
form.save()
messages.success(request, (title + ' has been updated.'))
return redirect('home')
else:
messages.error(request, 'Please correct the error(s) below.')
else:
form = NewsAndEventsForm(instance=instance)
return render(request, 'app/post_add.html', {
'title': 'Edit Post | DjangoSMS',
'form': form,
})
@login_required
@lecturer_required
def delete_post(request, pk):
post = get_object_or_404(NewsAndEvents, pk=pk)
title = post.title
post.delete()
messages.success(request, (title + ' has been deleted.'))
return redirect('home')
# ########################################################
# Session
# ########################################################
@login_required
@lecturer_required
def session_list_view(request):
""" Show list of all sessions """
sessions = Session.objects.all().order_by('-is_current_session', '-session')
return render(request, 'app/session_list.html', {"sessions": sessions})
@login_required
@lecturer_required
def session_add_view(request):
""" check request method, if POST we add session otherwise show empty form """
if request.method == 'POST':
form = SessionForm(request.POST)
if form.is_valid():
data = form.data.get('is_current_session') # returns string of 'True' if the user selected Yes
print(data)
if data == 'true':
sessions = Session.objects.all()
if sessions:
for session in sessions:
if session.is_current_session == True:
unset = Session.objects.get(is_current_session=True)
unset.is_current_session = False
unset.save()
form.save()
else:
form.save()
else:
form.save()
messages.success(request, 'Session added successfully. ')
return redirect('session_list')
else:
form = SessionForm()
return render(request, 'app/session_update.html', {'form': form})
@login_required
@lecturer_required
def session_update_view(request, pk):
session = Session.objects.get(pk=pk)
if request.method == 'POST':
form = SessionForm(request.POST, instance=session)
data = form.data.get('is_current_session')
if data == 'true':
sessions = Session.objects.all()
if sessions:
for session in sessions:
if session.is_current_session == True:
unset = Session.objects.get(is_current_session=True)
unset.is_current_session = False
unset.save()
if form.is_valid():
form.save()
messages.success(request, 'Session updated successfully. ')
return redirect('session_list')
else:
form = SessionForm(request.POST, instance=session)
if form.is_valid():
form.save()
messages.success(request, 'Session updated successfully. ')
return redirect('session_list')
else:
form = SessionForm(instance=session)
return render(request, 'app/session_update.html', {'form': form})
@login_required
@lecturer_required
def session_delete_view(request, pk):
session = get_object_or_404(Session, pk=pk)
if session.is_current_session:
messages.error(request, "You cannot delete current session")
return redirect('session_list')
else:
session.delete()
messages.success(request, "Session successfully deleted")
return redirect('session_list')
# ########################################################
# ########################################################
# Semester
# ########################################################
@login_required
@lecturer_required
def semester_list_view(request):
semesters = Semester.objects.all().order_by('-is_current_semester', '-semester')
return render(request, 'app/semester_list.html', {"semesters": semesters, })
@login_required
@lecturer_required
def semester_add_view(request):
if request.method == 'POST':
form = SemesterForm(request.POST)
if form.is_valid():
data = form.data.get('is_current_semester') # returns string of 'True' if the user selected Yes
if data == 'True':
semester = form.data.get('semester')
ss = form.data.get('session')
session = Session.objects.get(pk=ss)
try:
if Semester.objects.get(semester=semester, session=ss):
messages.error(request, semester + " semester in " + session.session + " session already exist")
return redirect('add_semester')
except:
semesters = Semester.objects.all()
sessions = Session.objects.all()
if semesters:
for semester in semesters:
if semester.is_current_semester == True:
unset_semester = Semester.objects.get(is_current_semester=True)
unset_semester.is_current_semester = False
unset_semester.save()
for session in sessions:
if session.is_current_session == True:
unset_session = Session.objects.get(is_current_session=True)
unset_session.is_current_session = False
unset_session.save()
new_session = request.POST.get('session')
set_session = Session.objects.get(pk=new_session)
set_session.is_current_session = True
set_session.save()
form.save()
messages.success(request, 'Semester added successfully.')
return redirect('semester_list')
form.save()
messages.success(request, 'Semester added successfully. ')
return redirect('semester_list')
else:
form = SemesterForm()
return render(request, 'app/semester_update.html', {'form': form})
@login_required
@lecturer_required
def semester_update_view(request, pk):
semester = Semester.objects.get(pk=pk)
if request.method == 'POST':
if request.POST.get('is_current_semester') == 'True': # returns string of 'True' if the user selected yes for 'is current semester'
unset_semester = Semester.objects.get(is_current_semester=True)
unset_semester.is_current_semester = False
unset_semester.save()
unset_session = Session.objects.get(is_current_session=True)
unset_session.is_current_session = False
unset_session.save()
new_session = request.POST.get('session')
form = SemesterForm(request.POST, instance=semester)
if form.is_valid():
set_session = Session.objects.get(pk=new_session)
set_session.is_current_session = True
set_session.save()
form.save()
messages.success(request, 'Semester updated successfully !')
return redirect('semester_list')
else:
form = SemesterForm(request.POST, instance=semester)
if form.is_valid():
form.save()
return redirect('semester_list')
else:
form = SemesterForm(instance=semester)
return render(request, 'app/semester_update.html', {'form': form})
@login_required
@lecturer_required
def semester_delete_view(request, pk):
semester = get_object_or_404(Semester, pk=pk)
if semester.is_current_semester:
messages.error(request, "You cannot delete current semester")
return redirect('semester_list')
else:
semester.delete()
messages.success(request, "Semester successfully deleted")
return redirect('semester_list')
# ########################################################
# from django.shortcuts import render_to_response
# from django.template import RequestContext
# def handler404(request, exception, template_name="common/404.html"):
# response = render_to_response("common/404.html")
# response.status_code = 404
# return response
# def handler500(request, *args, **argv):
# response = render_to_response('common/500.html', {}, context_instance=RequestContext(request))
# response.status_code = 500
# return response
# def handler400(request, exception, template_name="common/400.html"):
# response = render_to_response('common/400.html', context_instance=RequestContext(request))
# response.status_code = 400
# return response

0
course/__init__.py Normal file
View File

10
course/admin.py Normal file
View File

@ -0,0 +1,10 @@
from django.contrib import admin
from django.contrib.auth.models import Group
from .models import Program, Course, CourseAllocation, Upload
admin.site.register(Program)
admin.site.register(Course)
admin.site.register(CourseAllocation)
admin.site.register(Upload)

5
course/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class CourseConfig(AppConfig):
name = 'course'

105
course/forms.py Normal file
View File

@ -0,0 +1,105 @@
from django import forms
from django.db import transaction
from django.conf import settings
from django.contrib.auth.models import User
from accounts.models import User
from .models import Program, Course, CourseAllocation, Upload, UploadVideo
# User = settings.AUTH_USER_MODEL
class ProgramForm(forms.ModelForm):
class Meta:
model = Program
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'class': 'form-control'})
self.fields['summary'].widget.attrs.update({'class': 'form-control'})
class CourseAddForm(forms.ModelForm):
class Meta:
model = Course
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'class': 'form-control'})
self.fields['code'].widget.attrs.update({'class': 'form-control'})
# self.fields['courseUnit'].widget.attrs.update({'class': 'form-control'})
self.fields['credit'].widget.attrs.update({'class': 'form-control'})
self.fields['summary'].widget.attrs.update({'class': 'form-control'})
self.fields['program'].widget.attrs.update({'class': 'form-control'})
self.fields['level'].widget.attrs.update({'class': 'form-control'})
self.fields['year'].widget.attrs.update({'class': 'form-control'})
self.fields['semester'].widget.attrs.update({'class': 'form-control'})
class CourseAllocationForm(forms.ModelForm):
courses = forms.ModelMultipleChoiceField(
queryset=Course.objects.all().order_by('level'),
widget=forms.CheckboxSelectMultiple(attrs={'class': 'browser-default checkbox'}),
required=True
)
lecturer = forms.ModelChoiceField(
queryset=User.objects.filter(is_lecturer=True),
widget=forms.Select(attrs={'class': 'browser-default custom-select'}),
label="lecturer",
)
class Meta:
model = CourseAllocation
fields = ['lecturer', 'courses']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user')
super(CourseAllocationForm, self).__init__(*args, **kwargs)
self.fields['lecturer'].queryset = User.objects.filter(is_lecturer=True)
class EditCourseAllocationForm(forms.ModelForm):
courses = forms.ModelMultipleChoiceField(
queryset=Course.objects.all().order_by('level'),
widget=forms.CheckboxSelectMultiple,
required=True
)
lecturer = forms.ModelChoiceField(
queryset=User.objects.filter(is_lecturer=True),
widget=forms.Select(attrs={'class': 'browser-default custom-select'}),
label="lecturer",
)
class Meta:
model = CourseAllocation
fields = ['lecturer', 'courses']
def __init__(self, *args, **kwargs):
# user = kwargs.pop('user')
super(EditCourseAllocationForm, self).__init__(*args, **kwargs)
self.fields['lecturer'].queryset = User.objects.filter(is_lecturer=True)
# Upload files to specific course
class UploadFormFile(forms.ModelForm):
class Meta:
model = Upload
fields = ('title', 'file', 'course',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'class': 'form-control'})
self.fields['file'].widget.attrs.update({'class': 'form-control'})
# Upload video to specific course
class UploadFormVideo(forms.ModelForm):
class Meta:
model = UploadVideo
fields = ('title', 'video', 'course',)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update({'class': 'form-control'})
self.fields['video'].widget.attrs.update({'class': 'form-control'})

View File

@ -0,0 +1,67 @@
# Generated by Django 2.2.3 on 2020-07-29 15:25
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('app', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Course',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('slug', models.SlugField(blank=True, unique=True)),
('title', models.CharField(max_length=200, null=True)),
('code', models.CharField(max_length=200, null=True, unique=True)),
('credit', models.IntegerField(default=0, null=True)),
('summary', models.TextField(blank=True, max_length=200, null=True)),
('level', models.CharField(choices=[('Level course', 'Level course'), ('Bachloar', 'Bachloar'), ('Master', 'Master')], max_length=25, null=True)),
('year', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (4, '5'), (4, '6')], default=0)),
('semester', models.CharField(choices=[('First', 'First'), ('Second', 'Second'), ('Third', 'Third')], max_length=200)),
('is_elective', models.BooleanField(blank=True, default=False, null=True)),
],
),
migrations.CreateModel(
name='Program',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=150, unique=True)),
('summary', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='Upload',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('file', models.FileField(upload_to='course_files/', validators=[django.core.validators.FileExtensionValidator(['pdf', 'docx', 'doc', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar', '7zip'])])),
('updated_date', models.DateTimeField(auto_now=True, null=True)),
('upload_time', models.DateTimeField(auto_now_add=True, null=True)),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Course')),
],
),
migrations.CreateModel(
name='CourseAllocation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('courses', models.ManyToManyField(related_name='allocated_course', to='course.Course')),
('lecturer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='allocated_lecturer', to=settings.AUTH_USER_MODEL)),
('session', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='app.Session')),
],
),
migrations.AddField(
model_name='course',
name='program',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Program'),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2.3 on 2020-08-03 09:24
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('course', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='UploadVideo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('slug', models.SlugField(blank=True, unique=True)),
('video', models.FileField(upload_to='videos/', validators=[django.core.validators.FileExtensionValidator(['mp4', 'mkv', 'wmv', '3gp', 'f4v', 'avi', 'mp3'])])),
('summary', models.TextField(blank=True, null=True)),
('timestamp', models.DateTimeField(auto_now_add=True, null=True)),
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.Course')),
],
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 2.2.3 on 2020-08-03 10:35
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0002_uploadvideo'),
]
operations = [
migrations.AlterField(
model_name='uploadvideo',
name='video',
field=models.FileField(upload_to='course_videos/', validators=[django.core.validators.FileExtensionValidator(['mp4', 'mkv', 'wmv', '3gp', 'f4v', 'avi', 'mp3'])]),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2020-08-22 19:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('course', '0003_auto_20200803_1335'),
]
operations = [
migrations.AlterField(
model_name='course',
name='level',
field=models.CharField(choices=[('Bachloar', 'Bachloar Degree'), ('Master', 'Master Degree')], max_length=25, null=True),
),
]

View File

184
course/models.py Normal file
View File

@ -0,0 +1,184 @@
from django.db import models
from django.urls import reverse
from django.conf import settings
from django.core.validators import FileExtensionValidator
from django.db.models.signals import pre_save
from django.db.models import Q
from app.models import Session
from .utils import *
User = settings.AUTH_USER_MODEL
YEARS = (
(1, '1'),
(2, '2'),
(3, '3'),
(4, '4'),
(4, '5'),
(4, '6'),
)
# LEVEL_COURSE = "Level course"
BACHLOAR_DEGREE = "Bachloar"
MASTER_DEGREE = "Master"
LEVEL = (
# (LEVEL_COURSE, "Level course"),
(BACHLOAR_DEGREE, "Bachloar Degree"),
(MASTER_DEGREE, "Master Degree"),
)
FIRST = "First"
SECOND = "Second"
THIRD = "Third"
SEMESTER = (
(FIRST, "First"),
(SECOND, "Second"),
(THIRD, "Third"),
)
class ProgramManager(models.Manager):
def search(self, query=None):
qs = self.get_queryset()
if query is not None:
or_lookup = (Q(title__icontains=query) |
Q(summary__icontains=query)
)
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
return qs
class Program(models.Model):
title = models.CharField(max_length=150, unique=True)
summary = models.TextField(null=True, blank=True)
objects = ProgramManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('program_detail', kwargs={'pk': self.pk})
class CourseManager(models.Manager):
def search(self, query=None):
qs = self.get_queryset()
if query is not None:
or_lookup = (Q(title__icontains=query) |
Q(summary__icontains=query)|
Q(code__icontains=query)|
Q(slug__icontains=query)
)
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
return qs
class Course(models.Model):
slug = models.SlugField(blank=True, unique=True)
title = models.CharField(max_length=200, null=True)
code = models.CharField(max_length=200, unique=True, null=True)
credit = models.IntegerField(null=True, default=0)
summary = models.TextField(max_length=200, blank=True, null=True)
program = models.ForeignKey(Program, on_delete=models.CASCADE)
level = models.CharField(max_length=25, choices=LEVEL, null=True)
year = models.IntegerField(choices=YEARS, default=0)
semester = models.CharField(choices=SEMESTER, max_length=200)
is_elective = models.BooleanField(default=False, blank=True, null=True)
objects = CourseManager()
def __str__(self):
return "{0} ({1})".format(self.title, self.code)
def get_absolute_url(self):
return reverse('course_detail', kwargs={'slug': self.slug})
@property
def is_current_semester(self):
from app.models import Semester
current_semester = Semester.objects.get(is_current_semester=True)
if self.semester == current_semester.semester:
return True
else:
return False
def course_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(course_pre_save_receiver, sender=Course)
class CourseAllocation(models.Model):
lecturer = models.ForeignKey(User, on_delete=models.CASCADE, related_name='allocated_lecturer')
courses = models.ManyToManyField(Course, related_name='allocated_course')
session = models.ForeignKey(Session, on_delete=models.CASCADE, blank=True, null=True)
def __str__(self):
return self.lecturer.get_full_name
def get_absolute_url(self):
return reverse('edit_allocated_course', kwargs={'pk': self.pk})
class Upload(models.Model):
title = models.CharField(max_length=100)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
file = models.FileField(upload_to='course_files/', validators=[FileExtensionValidator(['pdf', 'docx', 'doc', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar', '7zip'])])
updated_date = models.DateTimeField(auto_now=True, auto_now_add=False, null=True)
upload_time = models.DateTimeField(auto_now=False, auto_now_add=True, null=True)
def __str__(self):
return str(self.file)[6:]
def get_extension_short(self):
ext = str(self.file).split(".")
ext = ext[len(ext)-1]
if ext == 'doc' or ext == 'docx':
return 'word'
elif ext == 'pdf':
return 'pdf'
elif ext == 'xls' or ext == 'xlsx':
return 'excel'
elif ext == 'ppt' or ext == 'pptx':
return 'powerpoint'
elif ext == 'zip' or ext == 'rar' or ext == '7zip':
return 'archive'
def delete(self, *args, **kwargs):
self.file.delete()
super().delete(*args, **kwargs)
class UploadVideo(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(blank=True, unique=True)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
video = models.FileField(upload_to='course_videos/', validators=[FileExtensionValidator(['mp4', 'mkv', 'wmv', '3gp', 'f4v', 'avi', 'mp3'])])
summary = models.TextField(null=True, blank=True)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True, null=True)
def __str__(self):
return str(self.title)
def get_absolute_url(self):
return reverse('video_single', kwargs={'slug': self.course.slug, 'video_slug': self.slug})
def delete(self, *args, **kwargs):
self.video.delete()
super().delete(*args, **kwargs)
def video_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(video_pre_save_receiver, sender=UploadVideo)

View File

@ -0,0 +1,88 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'programs' %}" class="primary1">Programs</a> <i>&rsaquo;</i> Add & Update Course</div>
<div class="title-1">Course Add & Update Form</div>
<div class="title-line"></div>
{% 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 %}
<form action="" method="POST">{% csrf_token %}
<div class="row">
<div class="col-md-6">
<div class="card">
<p class="form-title">Course Info</p>
<div class="p-3">
<div class="form-group">
<b>{{ form.title.label }}</b>{{ form.title }}
<span class="danger">{{ form.title.errors }}</span>
<p class="text-muted-xs">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</p>
</div>
<div class="form-group">
<b>{{ form.code.label }}</b>{{ form.code }}
<span class="danger">{{ form.code.errors }}</span>
<p class="text-muted-xs">The course code must be similar to only the pre requests.</p>
</div>
<div class="form-group">
<b>{{ form.summary.label }}</b>(optional){{ form.summary }}
<span class="danger">{{ form.summary.errors }}</span>
<p class="text-muted-xs">Enter some description about the course.</p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<p class="form-title">Applicant Info</p>
<div class="p-3">
<div class="form-group">
<b>{{ form.program.label }}</b>{{ form.program }}
<span class="danger">{{ form.program.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.credit.label }}</b>{{ form.credit }}
<span class="danger">{{ form.credit.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.year.label }}</b>{{ form.year }}
<span class="danger">{{ form.year.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.semester.label }}</b>{{ form.semester }}
<span class="danger">{{ form.semester.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.level.label }}</b>{{ form.level }}
<span class="danger">{{ form.level.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.is_elective.label }}</b>{{ form.is_elective }}
<span class="danger">{{ form.is_elective.errors }}</span>
</div>
</div>
</div>
</div>
</div>
<center><input class="btn btn-outline-primary mt-3" type="submit" value="Save"></center>
</form>
{% endblock content %}

View File

@ -0,0 +1,51 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'course_allocation_view' %}" class="primary1">Allocations</a> <i>&rsaquo;</i> Add</div>
<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 %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card">
<p class="form-title">Course Allocation Form</p>
<div class="p-3">
<form action="" method="POST">{% csrf_token %}
<!-- {{ form|crispy }} -->
<div class="form-group">
{{ form.lecturer.label }}{{ form.lecturer }}
<span class="danger">{{ form.lecturer.errors }}</span>
</div>
<div class="form-group">
{{ form.courses.label }}{{ form.courses }}
<span class="danger">{{ form.courses.errors }}</span>
</div>
{% for course in form.courses.all %}{{ course }}{% endfor %}
<input class="btn btn-outline-primary" type="submit" value="Save">
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,92 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Allocation list</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'course_allocation' %}"><i class="fas fa-plus"></i>Allocate Now</a>
{% endif %}
<div class="title-1"><i class="fas fa-tasks"></i>Course Allocation List</div>
<div class="title-line"></div>
{% 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 %}
<div class="content-center">
<form class="search-form" action="" method="POST">{% csrf_token %}
<input class="au-input" type="text" name="lecturer" placeholder="Lecturer" value="{{ request.GET.lecturer }}"/>
<input class="au-input" type="text" name="course" placeholder="Course" value="{{ request.GET.course }}"/>
<button class="btn btn-light" type="submit">
<i class="fas fa-search"></i> filter
</button>
</form>
</div>
<div class="table-responsive table-shadow p-0 mt-5">
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Lecturer</th>
<th>Courses</th>
{% if request.user.is_superuser %}
<th>Action</th>
{% endif %}
</tr>
</thead>
{% for course in allocated_courses %}
<tbody>
<tr>
<td> {{ forloop.counter }}.</td>
<td><a href="{{ course.lecturer.get_absolute_url }}">{{ course.lecturer.get_full_name }}</a></td>
<td><div class="flex">{% for i in course.courses.all %}
<div class="flex"><a class="edit-btn" href="{{ i.get_absolute_url }}">{{ i }}</a></div>
{% endfor %}</div>
</td>
{% if request.user.is_superuser %}
<td><div class="update-delete">
<a href="{% url 'edit_allocated_course' pk=course.pk %}" class="update" title="Edit or Update">
<i class="fas fa-edit"></i>
</a>
<a href="{% url 'course_deallocate' pk=course.pk %}" class="delete" title="Deallocate">
<i class="fas fa-trash-alt"></i>
</a>
</div>
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger"><i class="far fa-frown"></i>No Course Allocated.
{% if request.user.is_superuser %}
<a href="{% url 'course_allocation' %}">
<i class="primary" style="font-size: 22px;">
Allocate now
</i>
{% endif %}
</a>
</span>
</td>
</tr>
</tbody>
{% endfor %}
</table>
</div>
{% endblock content %}

View File

@ -0,0 +1,272 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Course Registration</div>
<p class="title-1">Course Add & Drop</p>
<div class="title-line mb-3"></div>
{% 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 %}
{% if not all_courses_are_registered %}
<form action="{% url 'course_registration' %}" method="POST">{% csrf_token %}
<div class="col-md-12 p-0 bg-white">
<p class="form-title">
<b>Course Add</b>
<div class="level-wrapper"><div class="info-text">{{ student.level }}</div></div>
</p>
<div class="container">
<div class="d-flex justify-content-between mb-3">
<button title="Save Score" type="submit" class="btn btn-primary"><i class="fa fa-plus"></i> Add Selected</button>
</div>
<div class="table-responsive p-0 px-2 mt-2">
<div class="table-title"><u>First Semester:</u></div>
<div class="table-shadow">
<table class="table">
<thead>
<tr>
<th>Mark</th>
<th>Course Code</th>
<th>Course Title</th>
<th>Cr.Hr(s)</th>
<th>Year</th>
<th>Classification</th>
<th>Elective Group</th>
</tr>
</thead>
<tbody>
{% for course in courses %}
{% if course.semester == "First" %}
<tr>
<th scope="row">
<input name="{{ course.pk }}" value="{{ course.courseUnit }}" type="checkbox">
</th>
<td>{{ course.code }}</td>
<td>{{ course.title }}</td>
<td>{{ course.credit }}</td>
<td>{{ course.year }}</td>
{% if course.is_elective %}
<td>Elective</td>
{% else %}
<td>Core</td>
{% endif %}
<th>-</th>
</tr>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Course.
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% endfor %}
<tr>
<th></th>
<td></td>
<td></td>
<td></td>
<td></td>
<td><b>First semester Credit(s):</b> {{ total_first_semester_credit }} </td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="table-responsive p-0 px-2 mt-2">
<div class="table-title"><u>Second Semester:</u></div>
<div class="table-shadow">
<table class="table">
<thead>
<tr>
<th>Mark</th>
<th>Course Code</th>
<th>Course Title</th>
<th>Cr.Hr(s)</th>
<th>Year</th>
<th>Classification</th>
<th>Elective Group</th>
</tr>
</thead>
<tbody>
{% for course in courses %}
{% if course.semester == "Second" %}
<tr>
<th scope="row">
<input name="{{ course.pk }}" value="{{ course.courseUnit }}" type="checkbox">
</th>
<td>{{ course.code }}</td>
<td>{{ course.title }}</td>
<td>{{ course.credit }}</td>
<td>{{ course.year }}</td>
{% if course.is_elective %}
<td>Elective</td>
{% else %}
<td>Core</td>
{% endif %}
<th>-</th>
</tr>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Course.
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% endfor %}
<tr>
<th></th>
<td></td>
<td></td>
<td></td>
<td></td>
<td><b>Second semester credit(s):</b> {{ total_sec_semester_credit }} </td>
<td></td>
</tr>
<tr>
<th scope="row"></th>
<td><b>Registerd course credit(s): <a id="units">{{ total_registered_credit }}</a></b></td>
<td></td>
<td></td>
<td></td>
<td><b>Total credit(s):</b> {{ total_sec_semester_credit|add:total_first_semester_credit }} </td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</form>
{% endif %}
<br>
<br>
{% if not no_course_is_registered %}
<a target="_blank" href="{% url 'course_registration_form' %}">
<span title="Print Registration Form" class="btn btn-warning">
<i class="fa fa-file-pdf-o" aria-hidden="true"></i><i class="fas fa-print"></i> Print Registration Form
</span>
</a>
<div class="col-md-12 p-0 bg-white">
<p class="form-title"><b>Course Drop</b>
<div class="level-wrapper"><div class="info-text">{{ student.level }}</div></div>
</p>
<div class="container">
<form action="{% url 'course_drop' %}" method="POST">
{% csrf_token %}
<div class="d-flex justify-content-between mb-4">
<button title="Save Score" type="submit" class="btn btn-primary">
<i class="fa fa-times"></i> Drop Selected
</button>
</div>
<!-- <div>
<a target="_blank" href="{% url 'course_registration_form' %}" class="btn btn-outline-white btn-rounded btn-sm px-2">
<i class="fa fa-file-pdf-o" aria-hidden="true"></i> Print Registration Form
</a>
</div> -->
<div class="table-responsive p-0 px-2 mt-2">
<div class="table-shadow">
<table class="table">
<thead>
<tr>
<th>Mark</th>
<th>Course Code</th>
<th>Course Title</th>
<th>Cr.Hr(s)</th>
<th>Year</th>
<th>Classification</th>
<th>Elective Group</th>
</tr>
</thead>
<tbody>
{% for course in registered_courses %}
<tr>
<th scope="row">
<input name="{{ course.pk }}" value="{{ course.courseUnit }}" type="checkbox">
</th>
<td>{{ course.code }}</td>
<td>{{ course.title }}</td>
<td>{{ course.credit }}</td>
<td>{{ course.year }}</td>
{% if course.is_elective %}
<td>Elective</td>
{% else %}
<td>Core</td>
{% endif %}
<th>-</th>
</tr>
{% empty %}
<tr>
<td></td>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Course.
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% endfor %}
<tr>
<th></th>
<td></td>
<td></td>
<td></td>
<td></td>
<td><b>Total credit(s):</b> {{ total_registered_credit }} </td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</form>
</div>
</div>
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,289 @@
{% extends 'base.html' %}
{% block title %}{{ title }} | DjangoSMS{% endblock title %}
{% load static %}
{% block content %}
<div class="container">
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home </a> <i>&rsaquo;</i>
<a href="{% url 'programs' %}" class="primary1">Programs </a> <i>&rsaquo;</i>
<a href="{% url 'program_detail' course.program.id %}" class="primary1"> {{ course.program }}</a> <i>&rsaquo;</i> {{ course }}
</div>
<div class="row">
<div class="col ml-auto">
{% if request.user.is_superuser or request.user.is_lecturer %}
<a class="add-button" href="{% url 'upload_file_view' course.slug %}"><i class="fas fa-plus"></i>Upload new file</a>
<a class="add-button" href="{% url 'upload_video' course.slug %}"><i class="fas fa-plus"></i>Upload new video</a>
{% endif %}
</div>
<div class="col text-right">
<a class="btn btn-lg btn-warning" href="{% url 'quiz_index' course.slug %}"><i class="fas fa-list"></i> Take a Quiz</a>
</div>
</div>
<div class="title-1">{{ course }}</div>
<div class="title-line"></div><br>
<p class="program-description">{{ course.summary }}</p>
{% if request.user.is_superuser %}
<div class="btn-flex">
<a class="edit-btn" href="{% url 'edit_course' course.slug %}">
<i class="fas fa-pencil-alt"></i><span class="mobile-hide">Edit This course</span>
</a>
</div>
{% endif %}
{% 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 %}
<!-- <div class="row">
<div class="col-md-12 mb-5 p-0">
<p class="form-title m-0">Instructor(s)</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Profile Pics</th>
<th>Full Name</th>
<th>Email</th>
{% if request.user.is_superuser %}
<th>Action</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for lecturer in lecturers %}
<tr>
<td>{{ forloop.counter }}</td>
<th><img class="img-round" src="{{ lecturer.lecturer.picture.url }}"></th>
<td>{% if request.user.is_superuser %}
<a href="{{ lecturer.lecturer.get_absolute_url }}" title="See the profile">
{{ lecturer|title }}
</a>
{% else %}
{{ lecturer|title }}
{% endif %}
</td>
<td>{{ lecturer.lecturer.email }}</td>
{% if request.user.is_superuser %}
<td><div class="update-delete">
<a href="{% url 'edit_allocated_course' pk=lecturer.pk %}" class="update" title="Update or Edit">
<i class="fas fa-pencil-alt"></i>
</a>
</div>
</td>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown"></i>No Lecturer Assigned for this course.
{% if request.user.is_superuser %}
<a href="{% url 'course_allocation' %}">
<i class="primary" style="font-size: 22px;">
Assign now.
</i>
{% endif %}
</a>
</span>
</td>
<td></td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div> -->
<div class="row mb-5">
<div class="col-md-12 p-0">
<p class="form-title m-0">Video Tutorials</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Video Title</th>
<th>Uploaded Date</th>
<th>Get Started</th>
{% if request.user.is_superuser or request.user.is_lecturer %}
<th>Actions</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for video in videos %}
<tr>
<td>{{ forloop.counter }}</td>
<td><a href="{{ video.get_absolute_url }}" title="{{ video }}">
<i style="font-size: 20px; padding-right: 10px;" class="fas fa-video"></i>
{{ video.title|title }}
</a>
</td>
<td>{{ video.timestamp|date }}</td>
<th>
<div>
<a class="download-btn" href="{{ video.get_absolute_url }}" title="Download to your device">
<i class="fas fa-download"></i>Get Started</a>
</div>
</th>
{% if request.user.is_superuser or request.user.is_lecturer %}
<td> <div class="update-delete">
<a href="{% url 'upload_video_edit' slug=course.slug video_slug=video.slug %}" class="update" title="Edit">
<i class="fas fa-pencil-alt"></i>
</a>
<a href="{% url 'upload_video_delete' slug=course.slug video_slug=video.slug %}" class="delete" title="Delete">
<i class="fas fa-trash-alt"></i>
</a>
</div>
</td>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown fa-1x"></i> No video Uploaded.
{% if request.user.is_superuser or request.user.is_lecturer %}
<a href="{% url 'upload_video' course.slug %}">
<i class="primary" style="font-size: 22px;">
Upload now.
</i>
{% endif %}
</a>
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 p-0">
<p class="form-title m-0">Documentations</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>#</th>
<th>File name</th>
<th>Uploaded Date</th>
<th>Updated Date</th>
<th>Downloads</th>
{% if request.user.is_superuser or request.user.is_lecturer %}
<th>Actions</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<td>{{ forloop.counter }}</td>
<td><a href="{{ file.file.url }}" title="{{ file }}">
<i style="font-size: 20px; padding-right: 10px;" class="fas fa-file-{{ file.get_extension_short }}"></i>
{{ file.title|title }}
</a>
</td>
<td>{{ file.upload_time|date }}</td>
<td>{{ file.updated_date|date }}</td>
<th>
<div>
<a class="download-btn" href="{{ file.file.url }}" title="Download to your device">
<i class="fas fa-download"></i>Download</a>
</div>
</th>
{% if request.user.is_superuser or request.user.is_lecturer %}
<td> <div class="update-delete">
<a href="{% url 'upload_file_edit' slug=course.slug file_id=file.pk %}" class="update" title="Edit">
<i class="fas fa-pencil-alt"></i>
</a>
<a href="{% url 'upload_file_delete' slug=course.slug file_id=file.pk %}" class="delete" title="Delete">
<i class="fas fa-trash-alt"></i>
</a>
</div>
</td>
{% endif %}
{% empty %}
<tr>
<td></td>
<td></td>
<td>
<span class="text-danger">
<i class="far fa-frown fa-1x"></i> No File Uploaded.
{% if request.user.is_superuser or request.user.is_lecturer %}
<a href="{% url 'upload_file_view' course.slug %}">
<i class="primary" style="font-size: 22px;">
Upload now.
</i>
{% endif %}
</a>
</span>
</td>
<td></td>
<td></td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="site-section">
<div class="title-1">Instructor(s)</div>
<div class="title-line"></div><br>
<div class="container marketing">
<div class="row">
{% for lecturer in lecturers %}
<div class="col-lg-4 mx-auto" style="background: transparent; box-shadow: none;">
<img class="" src="{{ lecturer.lecturer.picture.url }}" alt="" style="width:140px;">
<h2>{{ lecturer|title }}</h2>
<p style="color: #6c757d;">{{ lecturer.lecturer.email }}</p>
<p>Donec sed odio dui. Etiam porta sem malesuada magna mollis euismod. Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
<p>
<a class="btn rounded-circle btn-secondary" href="#" role="button"><i class="fab fa-twitter"></i></a>
<a class="btn rounded-circle btn-secondary" href="#" role="button"><i class="fab fa-facebook-f"></i></a>
<a class="btn rounded-circle btn-secondary" href="#" role="button"><i class="fab fa-linkedin-in"></i></a>
</p>
<!-- <p><a class="btn btn-secondary" href="#" role="button">View details &raquo;</a></p> -->
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,48 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'programs' %}" class="primary1">Programs</a> <i>&rsaquo;</i> Add</div>
<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 %}
<div class="row">
<div class="col-md-6 mx-auto">
<div class="card">
<p class="form-title">Program Add Form</p>
<div class="p-3"><br>
<form action="" method="POST">{% csrf_token %}
<!-- {{ form|crispy }} -->
<div class="form-group">
<b>{{ form.title.label }}</b>{{ form.title }}
<span class="danger">{{ form.title.errors }}</span>
</div>
<div class="form-group">
<b>{{ form.summary.label }}</b>{{ form.summary }}
<span class="danger">{{ form.summary.errors }}</span>
</div>
<input class="btn btn-outline-primary" type="submit" value="Save">
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,115 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Programs</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'add_program' %}"><i class="fas fa-plus"></i>Add Program</a>
{% endif %}
<div class="title-1"><i class="fas fa-book-open"></i>Program List</div>
<div class="title-line"></div>
{% 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 %}
<div class="content-center">
<form class="search-form mx-auto" action="" method="GET">{% csrf_token %}
<input class="au-input" type="text" name="program_filter" placeholder="Program name" value="{{ request.GET.program_filter }}"/>
<button class="btn btn-light" type="submit">
<i class="fas fa-search"></i> Filter
</button>
</form>
</div>
<!-- <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
Launch demo modal
</button> -->
<!-- Modal -->
{% for program in programs %}
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<p class="p-4">
Are you sure you want to delete this item?
</p>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<a class="btn btn-danger" href="{% url 'program_delete' pk=program.pk %}">Delete</a>
</div>
</div>
</div>
</div>
{% endfor %}
<div class="table-responsive p-0 px-2 mt-5">
<div class="table-shadow">
<table class="table">
<thead>
<tr>
<th>#</th>
<th>Program Name</th>
<th>Summary</th>
{% if request.user.is_superuser %}
<th>Action</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for program in programs %}
<tr>
<td>{{ forloop.counter }}.</td>
<td><a class="a-list" href="{{ program.get_absolute_url }}">
{{ program.title}}</a></td>
<td>{{ program.summary }} </td>
{% if request.user.is_superuser %}
<td>
<div class="update-delete">
<a href="{% url 'edit_program' pk=program.pk %}" class="update"><i class="fas fa-edit"></i></a>
<button type="button" class="delete" data-toggle="modal" data-target="#exampleModal">
<i class="fas fa-trash-alt"></i>
</button>
</div>
<!-- Modal -->
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<p class="p-4">
Are you sure you want to delete this program?
</p>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<a class="btn btn-danger" href="{% url 'program_delete' pk=program.pk %}">Delete</a>
</div>
</div>
</div>
</div>
<!-- <td> <div class="update-delete">
<a href="{% url 'edit_program' pk=program.pk %}" class="update"><i class="fas fa-edit"></i></a>
<a href="{% url 'program_delete' pk=program.pk %}" class="delete"><i class="fas fa-trash-alt"></i></a>
</div> -->
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,111 @@
{% extends 'base.html' %}
{% block title %} {{ title }} | DjangoSMS{% endblock title %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'programs' %}" class="primary1">Programs</a> <i>&rsaquo;</i> {{ program.title }}</div>
{% if request.user.is_superuser %}
<a class="add-button" href="{% url 'course_add' pk=program.pk %}"><i class="fas fa-plus"></i>Add Course</a>
{% endif %}
{% if program %}
<div class="title-1">{{ program.title }}</div>
<div class="title-line"></div>
{% if program.summary %}
<p class="program-description">{{ program.summary }}</p>
{% endif %}
{% endif %}
{% 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 %}
<div class="content-center">
<form class="search-form" action="" method="GET">
<input class="au-input" type="text" name="name" placeholder="Course Name" value="{{ request.GET.name }}"/>
<input class="au-input" type="text" name="code" placeholder="Course Code" value="{{ request.GET.code }}"/>
<input class="au-input" type="number" name="year" placeholder="Course Year" value="{{ request.GET.year }}"/>
<button class="btn btn-light" type="submit">
<i class="fas fa-search"></i> filter
</button>
</form>
</div>
<div class="table-responsive p-0 px-2 mt-5">
<div class="table-shadow">
<table class="table">
<thead>
<tr>
<th>#</th>
<th> Course Name </th>
<th> Course Code </th>
<th> Cr.Hr </th>
<th> Level </th>
<th> Year </th>
<th> Semester </th>
<th> Current Semester </th>
{% if request.user.is_superuser %}
<th>Action</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for course in courses %}
<tr>
<td>{{ forloop.counter }}.</td>
<td><a href="{{ course.get_absolute_url }}">
{{ course.title }}</a></td>
<td>{{ course.code }}</td>
<td>{{ course.credit }}</td>
<td>{{ course.level }}</td>
<td>{{ course.year }}</td>
<td>{{ course.semester }}</td>
<th>{% if course.is_current_semester == False %}<i class="fas fa-times-circle fa-1-5x danger"></i>
{% elif course.is_current_semester == True %}<i class="fas fa-check-circle fa-1-5x"></i>
{% endif %}
</th>
{% if request.user.is_superuser %}
<td> <div class="update-delete">
<a href="{% url 'edit_course' slug=course.slug %}" class="update"><i class="fas fa-edit"></i></a>
<a href="{% url 'delete_course' slug=course.slug %}" class="delete"><i class="fas fa-trash-alt"></i></a>
</div>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if courses.paginator.page_range|length > 1 %}
<div class="content-center">
<div class="pagination">
<a href="?page=1">&laquo;</a>
{% for i in courses.paginator.page_range %}
{% if i == courses.number %}
<a class="pagination-active" href="?page={{ i }}"><b>{{ i }}</b></a>
{% else %}
<a href="?page={{ i }}">{{ i }}</a>
{% endif %}
{% endfor %}
<a href="?page={{ courses.paginator.num_pages }}">&raquo;</a>
</div>
</div>
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,136 @@
{% extends 'base.html' %}
{% block title %} My Courses | DjangoSMS{% endblock title %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> My Courses</div>
{% if request.user.is_student %}
<div class="title-1">{{ student.department.title }}</div>
<div class="title-line"></div>
{% if student.department.summary %}
<p class="program-description">{{ student.department.summary }}</p>
{% endif %}
{% endif %}
{% if request.user.is_lecturer %}
<div class="title-1">My Courses</div>
<div class="title-line"></div>
{% endif %}
{% 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 %}
{% if request.user.is_student %}
<div class="table-responsive p-3 mt-3">
<div class="table-title"><u>Taken Courses:</u></div>
<div class="table-shadow">
<table class="table">
<thead>
<tr>
<th>#</th>
<th> Course Name </th>
<th> Course Code </th>
<th> Cr.Hr </th>
<th> Year </th>
<th> Semester </th>
<th> Current Semester </th>
<th> Taken </th>
</tr>
</thead>
<tbody>
{% for course in taken_courses %}
<tr>
<td>{{ forloop.counter }}.</td>
<td><a href="{{ course.get_absolute_url }}">
{{ course.course.title }}</a></td>
<td>{{ course.course.code }}</td>
<td>{{ course.course.credit }}</td>
<td>{{ course.course.year }}</td>
<td>{{ course.course.semester }}</td>
<th>
{% if course.course.is_current_semester == False %}
<i class="fas fa-times-circle fa-1-5x danger"></i>
{% else %}
<i class="fas fa-check-circle"></i>
{% endif %}
</th>
<td class="success">
<i class="fas fa-check-circle"></i> Taken
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
<div class="table-responsive p-3">
<div class="table-title"><u>All Courses:</u></div>
<div class="table-shadow">
<table class="table">
<thead>
<tr>
<th>#</th>
<th> Course Name </th>
<th> Course Code </th>
<th> Cr.Hr </th>
<th> Year </th>
<th> Semester </th>
<th> Current Semester </th>
</tr>
</thead>
<tbody>
{% for course in courses %}
<tr>
<td>{{ forloop.counter }}.</td>
<td><a href="{{ course.get_absolute_url }}">
{{ course.title }}</a></td>
<td>{{ course.code }}</td>
<td>{{ course.credit }}</td>
<td>{{ course.year }}</td>
<td>{{ course.semester }}</td>
<th>
{% if course.is_current_semester == False %}
<i class="fas fa-times-circle fa-1-5x danger"></i>
{% else %}
<i class="fas fa-check-circle"></i>
{% endif %}
</th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if courses.paginator.page_range|length > 1 %}
<div class="content-center">
<div class="pagination">
<a href="?page=1">&laquo;</a>
{% for i in courses.paginator.page_range %}
{% if i == courses.number %}
<a class="pagination-active" href="?page={{ i }}"><b>{{ i }}</b></a>
{% else %}
<a href="?page={{ i }}">{{ i }}</a>
{% endif %}
{% endfor %}
<a href="?page={{ courses.paginator.num_pages }}">&raquo;</a>
</div>
</div>
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,63 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a>
<i>&rsaquo;</i> <a href="{% url 'programs' %}" class="primary1">Programs</a> <i>&rsaquo;</i>
<a href="{% url 'program_detail' course.program.id %}" class="primary1"> {{ course.program }}</a> <i>&rsaquo;</i>
<a href="{% url 'course_detail' course.slug %}" class="primary1">{{ course }}</a>
<i>&rsaquo;</i> file upload</div>
<p class="title-1">File upload for {{ course }}</p>
<div class="title-line"></div><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 %}
<div class="row">
<div class="col-md-8 p-0 mx-auto">
<div class="card">
<p class="form-title">File Upload Form</p><br>
<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}
<div class="container">
<div class="form-group">
<b>File Name</b>{{ form.title }}
<span class="danger">{{ form.title.errors }}</span>
</div>
<div class="form-group">
<b>File</b><br>{{ form.file }}
<span class="danger">{{ form.file.errors }}</span>
</div>
<p class="text-orange"><b>Valid Files: </b>pdf, docx, doc, xls, xlsx, ppt, pptx, zip, rar, 7zip</p>
<div class="form-group">
<select hidden name="course" required="" id="id_course">
<option value="{{ course.pk }}" selected>{{ course }}</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit">Upload</button>
<a class="btn btn-danger" href="{% url 'course_detail' course.slug %}" style="float: right;">Cancel</a>
</div>
</div>
<br>
<div class="card">
</form>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,63 @@
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock title %}
{% load crispy_forms_tags %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a>
<i>&rsaquo;</i> <a href="{% url 'programs' %}" class="primary1">Programs</a> <i>&rsaquo;</i>
<a href="{% url 'program_detail' course.program.id %}" class="primary1"> {{ course.program }}</a> <i>&rsaquo;</i>
<a href="{% url 'course_detail' course.slug %}" class="primary1">{{ course }}</a>
<i>&rsaquo;</i> Video upload</div>
<p class="title-1">Video upload for {{ course }}</p>
<div class="title-line"></div><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 %}
<div class="row">
<div class="col-md-8 p-0 mx-auto">
<div class="card">
<p class="form-title">Video Upload Form</p><br>
<form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}
<div class="container">
<div class="form-group">
<b>Video Name</b>{{ form.title }}
<span class="danger">{{ form.title.errors }}</span>
</div>
<div class="form-group">
<b>Video</b><br>{{ form.video }}
<span class="danger">{{ form.video.errors }}</span>
</div>
<p class="text-orange"><b>Valid video formats: </b>'mp4', 'mkv', 'wmv', '3gp', 'f4v', 'avi', 'mp3'</p>
<div class="form-group">
<select hidden name="course" required="" id="id_course">
<option value="{{ course.pk }}" selected>{{ course }}</option>
</select>
</div>
<div class="form-group">
<button class="btn btn-primary" type="submit">Upload</button>
<a class="btn btn-danger" href="{% url 'course_detail' course.slug %}" style="float: right;">Cancel</a>
</div>
</div>
<br>
<div class="card">
</form>
</div>
</div>
{% endblock content %}

View File

@ -0,0 +1,33 @@
{% extends 'base.html' %}
{% block title %}{{ video.title }} | DjangoSMS{% endblock title %}
{% load static %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a>
<i>&rsaquo;</i> <a href="{% url 'programs' %}" class="primary1">Programs</a> <i>&rsaquo;</i>
<a href="{% url 'program_detail' video.course.program.id %}" class="primary1"> {{ video.course.program }}</a> <i>&rsaquo;</i>
<a href="{% url 'course_detail' video.course.slug %}" class="primary1">{{ video.course }}</a>
<i>&rsaquo;</i> {{ video.title }}</div>
<p class="title-1">{{ video.title }}</p>
<div class="title-line"></div><br>
<p class="text-orange text-center">Video tutorial for course {{ video.course }}</p>
<style>
video{
max-width: 100%;
-webkit-box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
box-shadow: 0px 2px 5px 0px rgba(0,0,0,0.16), 0px 2px 10px 0px rgba(0,0,0,0.12);
}
</style>
<div class="col-md-10 mx-auto d-block">
<p><i class="fas fa-calendar"></i> {{ video.timestamp|timesince }} ago</p>
<div class=""><video src="{{ video.video.url }}" controls ></video></div>
</div>
<p class="text-orange text-center">{{ video.summary }}</p>
{% endblock content %}

3
course/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

53
course/urls.py Normal file
View File

@ -0,0 +1,53 @@
from django.conf.urls import url
from django.urls import path
from django.contrib.auth.views import (
PasswordResetView, PasswordResetDoneView, PasswordResetConfirmView,
PasswordResetCompleteView, LoginView, LogoutView
)
# from .views import (
# program_view, program_detail, program_add, program_edit, program_delete,
# course_single, course_add, course_edit, course_delete,
# CourseAllocationFormView, course_allocation_view, edit_allocated_course, deallocate_course,
# handle_file_upload, handle_file_edit, handle_file_delete,
# course_registration, course_drop, user_course_list
# )
from .views import *
urlpatterns = [
# Program urls
url(r'^$', program_view, name='programs'),
url(r'^(?P<pk>\d+)/detail/$', program_detail, name='program_detail'),
url(r'^add/$', program_add, name='add_program'),
url(r'^(?P<pk>\d+)/edit/$', program_edit, name='edit_program'),
url(r'^(?P<pk>\d+)/delete/$', program_delete, name='program_delete'),
# Course urls
url(r'^course/(?P<slug>[\w-]+)/detail/$', course_single, name='course_detail'),
url(r'^(?P<pk>\d+)/course/add/$', course_add, name='course_add'),
url(r'^course/(?P<slug>[\w-]+)/edit/$', course_edit, name='edit_course'),
url(r'^course/delete/(?P<slug>[\w-]+)/$', course_delete, name='delete_course'),
# CourseAllocation urls
url(r'^course/assign/$', CourseAllocationFormView.as_view(), name='course_allocation'),
url(r'^course/allocated/$', course_allocation_view, name='course_allocation_view'),
url(r'^allocated_course/(?P<pk>\d+)/edit/$', edit_allocated_course, name='edit_allocated_course'),
url(r'^course/(?P<pk>\d+)/deallocate/$', deallocate_course, name='course_deallocate'),
# File uploads urls
url(r'^course/(?P<slug>[\w-]+)/documentations/upload/$', handle_file_upload, name='upload_file_view'),
url(r'^course/(?P<slug>[\w-]+)/documentations/(?P<file_id>\d+)/edit/$', handle_file_edit, name='upload_file_edit'),
url(r'^course/(?P<slug>[\w-]+)/documentations/(?P<file_id>\d+)/delete/$', handle_file_delete, name='upload_file_delete'),
# Video uploads urls
url(r'^course/(?P<slug>[\w-]+)/video_tutorials/upload/$', handle_video_upload, name='upload_video'),
url(r'^course/(?P<slug>[\w-]+)/video_tutorials/(?P<video_slug>[\w-]+)/detail/$', handle_video_single, name='video_single'),
url(r'^course/(?P<slug>[\w-]+)/video_tutorials/(?P<video_slug>[\w-]+)/edit/$', handle_video_edit, name='upload_video_edit'),
url(r'^course/(?P<slug>[\w-]+)/video_tutorials/(?P<video_slug>[\w-]+)/delete/$', handle_video_delete, name='upload_video_delete'),
# course registration
url(r'^course/registration/$', course_registration, name='course_registration'),
url(r'^course/drop/$', course_drop, name='course_drop'),
url(r'^my_courses/$', user_course_list, name="user_course_list"),
]

31
course/utils.py Normal file
View File

@ -0,0 +1,31 @@
import datetime
import os
import random
import string
from django.utils.text import slugify
def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def unique_slug_generator(instance, new_slug=None):
"""
This is for a Django project and it assumes your instance
has a model with a slug field and a title character (char) field.
"""
if new_slug is not None:
slug = new_slug
else:
slug = slugify(instance.title)
Klass = instance.__class__
qs_exists = Klass.objects.filter(slug=slug).exists()
if qs_exists:
new_slug = "{slug}-{randstr}".format(
slug=slug,
randstr=random_string_generator(size=4)
)
return unique_slug_generator(instance, new_slug=new_slug)
return slug

475
course/views.py Normal file
View File

@ -0,0 +1,475 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.db.models import Sum, Avg, Max, Min, Count
from django.contrib.auth.decorators import login_required
from django.views.generic import CreateView
from django.core.paginator import Paginator
from django.conf import settings
from django.utils.decorators import method_decorator
from django.views.generic import ListView
from accounts.models import User, Student
from app.models import Session, Semester
from result.models import TakenCourse
from accounts.decorators import lecturer_required, student_required
from .forms import (
ProgramForm, CourseAddForm, CourseAllocationForm,
EditCourseAllocationForm, UploadFormFile, UploadFormVideo
)
from .models import Program, Course, CourseAllocation, Upload, UploadVideo
# ########################################################
# Program views
# ########################################################
@login_required
def program_view(request):
programs = Program.objects.all()
program_filter = request.GET.get('program_filter')
if program_filter:
programs = Program.objects.filter(title__icontains=program_filter)
return render(request, 'course/program_list.html', {
'title': "Programs | DjangoSMS",
'programs': programs,
})
@login_required
@lecturer_required
def program_add(request):
if request.method == 'POST':
form = ProgramForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, request.POST.get('title') + ' program has been created.')
return redirect('programs')
else:
messages.error(request, 'Correct the error(S) below.')
else:
form = ProgramForm()
return render(request, 'course/program_add.html', {
'title': "Add Program | DjangoSMS",
'form': form,
})
@login_required
def program_detail(request, pk):
program = Program.objects.get(pk=pk)
courses = Course.objects.filter(program_id=pk).order_by('-year')
credits = Course.objects.aggregate(Sum('credit'))
paginator = Paginator(courses, 10)
page = request.GET.get('page')
courses = paginator.get_page(page)
return render(request, 'course/program_single.html', {
'title': program.title,
'program': program, 'courses': courses, 'credits': credits
}, )
@login_required
@lecturer_required
def program_edit(request, pk):
program = Program.objects.get(pk=pk)
if request.method == 'POST':
form = ProgramForm(request.POST, instance=program)
if form.is_valid():
form.save()
messages.success(request, str(request.POST.get('title')) + ' program has been updated.')
return redirect('programs')
else:
form = ProgramForm(instance=program)
return render(request, 'course/program_add.html', {
'title': "Edit Program | DjangoSMS",
'form': form
})
@login_required
@lecturer_required
def program_delete(request, pk):
program = Program.objects.get(pk=pk)
title = program.title
program.delete()
messages.success(request, 'Program ' + title + ' has been deleted.')
return redirect('programs')
# ########################################################
# ########################################################
# Course views
# ########################################################
@login_required
def course_single(request, slug):
course = Course.objects.get(slug=slug)
files = Upload.objects.filter(course__slug=slug)
videos = UploadVideo.objects.filter(course__slug=slug)
# lecturers = User.objects.filter(allocated_lecturer__pk=course.id)
lecturers = CourseAllocation.objects.filter(courses__pk=course.id)
return render(request, 'course/course_single.html', {
'title': course.title,
'course': course,
'files': files,
'videos': videos,
'lecturers': lecturers,
'media_url': settings.MEDIA_ROOT,
}, )
@login_required
@lecturer_required
def course_add(request, pk):
users = User.objects.all()
if request.method == 'POST':
form = CourseAddForm(request.POST)
course_name = request.POST.get('title')
course_code = request.POST.get('code')
if form.is_valid():
form.save()
messages.success(request, (course_name + '(' + course_code + ')' + ' has been created.'))
return redirect('program_detail', pk=request.POST.get('program'))
else:
messages.error(request, 'Correct the error(s) below.')
else:
form = CourseAddForm(initial={'program': Program.objects.get(pk=pk)})
return render(request, 'course/course_add.html', {
'title': "Add Course | DjangoSMS",
'form': form, 'program': pk, 'users': users
}, )
@login_required
@lecturer_required
def course_edit(request, slug):
course = get_object_or_404(Course, slug=slug)
if request.method == 'POST':
form = CourseAddForm(request.POST, instance=course)
course_name = request.POST.get('title')
course_code = request.POST.get('code')
if form.is_valid():
form.save()
messages.success(request, (course_name + '(' + course_code + ')' + ' has been updated.'))
return redirect('program_detail', pk=request.POST.get('program'))
else:
messages.error(request, 'Correct the error(s) below.')
else:
form = CourseAddForm(instance=course)
return render(request, 'course/course_add.html', {
'title': "Edit Course | DjangoSMS",
# 'form': form, 'program': pk, 'course': pk
'form': form
}, )
@login_required
@lecturer_required
def course_delete(request, slug):
course = Course.objects.get(slug=slug)
# course_name = course.title
course.delete()
messages.success(request, 'Course ' + course.title + ' has been deleted.')
return redirect('program_detail', pk=course.program.id)
# ########################################################
# ########################################################
# Course Allocation
# ########################################################
@method_decorator([login_required], name='dispatch')
class CourseAllocationFormView(CreateView):
form_class = CourseAllocationForm
template_name = 'course/course_allocation_form.html'
def get_form_kwargs(self):
kwargs = super(CourseAllocationFormView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
# if a staff has been allocated a course before update it else create new
lecturer = form.cleaned_data['lecturer']
selected_courses = form.cleaned_data['courses']
courses = ()
for course in selected_courses:
courses += (course.pk,)
# print(courses)
try:
a = CourseAllocation.objects.get(lecturer=lecturer)
except:
a = CourseAllocation.objects.create(lecturer=lecturer)
for i in range(0, selected_courses.count()):
a.courses.add(courses[i])
a.save()
return redirect('course_allocation_view')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['title'] = "Assign Course | DjangoSMS"
return context
@login_required
def course_allocation_view(request):
allocated_courses = CourseAllocation.objects.all()
return render(request, 'course/course_allocation_view.html', {
'title': "Course Allocations | DjangoSMS",
"allocated_courses": allocated_courses
})
@login_required
@lecturer_required
def edit_allocated_course(request, pk):
allocated = get_object_or_404(CourseAllocation, pk=pk)
if request.method == 'POST':
form = EditCourseAllocationForm(request.POST, instance=allocated)
if form.is_valid():
form.save()
messages.success(request, 'course assigned has been updated.')
return redirect('course_allocation_view')
else:
form = EditCourseAllocationForm(instance=allocated)
return render(request, 'course/course_allocation_form.html', {
'title': "Edit Course Allocated | DjangoSMS",
'form': form, 'allocated': pk
}, )
@login_required
@lecturer_required
def deallocate_course(request, pk):
course = CourseAllocation.objects.get(pk=pk)
course.delete()
messages.success(request, 'successfully deallocate!')
return redirect("course_allocation_view")
# ########################################################
# ########################################################
# File Upload views
# ########################################################
@login_required
@lecturer_required
def handle_file_upload(request, slug):
course = Course.objects.get(slug=slug)
if request.method == 'POST':
form = UploadFormFile(request.POST, request.FILES, {'course': course})
# file_name = request.POST.get('name')
if form.is_valid():
form.save()
messages.success(request, (request.POST.get('title') + ' has been uploaded.'))
return redirect('course_detail', slug=slug)
else:
form = UploadFormFile()
return render(request, 'upload/upload_file_form.html', {
'title': "File Upload | DjangoSMS",
'form': form, 'course': course
})
@login_required
@lecturer_required
def handle_file_edit(request, slug, file_id):
course = Course.objects.get(slug=slug)
instance = Upload.objects.get(pk=file_id)
if request.method == 'POST':
form = UploadFormFile(request.POST, request.FILES, instance=instance)
# file_name = request.POST.get('name')
if form.is_valid():
form.save()
messages.success(request, (request.POST.get('title') + ' has been updated.'))
return redirect('course_detail', slug=slug)
else:
form = UploadFormFile(instance=instance)
return render(request, 'upload/upload_file_form.html', {
'title': instance.title,
'form': form, 'course': course})
def handle_file_delete(request, slug, file_id):
file = Upload.objects.get(pk=file_id)
# file_name = file.name
file.delete()
messages.success(request, (file.title + ' has been deleted.'))
return redirect('course_detail', slug=slug)
# ########################################################
# Video Upload views
# ########################################################
@login_required
@lecturer_required
def handle_video_upload(request, slug):
course = Course.objects.get(slug=slug)
if request.method == 'POST':
form = UploadFormVideo(request.POST, request.FILES, {'course': course})
if form.is_valid():
form.save()
messages.success(request, (request.POST.get('title') + ' has been uploaded.'))
return redirect('course_detail', slug=slug)
else:
form = UploadFormVideo()
return render(request, 'upload/upload_video_form.html', {
'title': "Video Upload | DjangoSMS",
'form': form, 'course': course
})
@login_required
# @lecturer_required
def handle_video_single(request, slug, video_slug):
course = get_object_or_404(Course, slug=slug)
video = get_object_or_404(UploadVideo, slug=video_slug)
return render(request, 'upload/video_single.html', {'video': video})
@login_required
@lecturer_required
def handle_video_edit(request, slug, video_slug):
course = Course.objects.get(slug=slug)
instance = UploadVideo.objects.get(slug=video_slug)
if request.method == 'POST':
form = UploadFormVideo(request.POST, request.FILES, instance=instance)
if form.is_valid():
form.save()
messages.success(request, (request.POST.get('title') + ' has been updated.'))
return redirect('course_detail', slug=slug)
else:
form = UploadFormVideo(instance=instance)
return render(request, 'upload/upload_video_form.html', {
'title': instance.title,
'form': form, 'course': course})
def handle_video_delete(request, slug, video_slug):
video = get_object_or_404(UploadVideo, slug=video_slug)
# video = UploadVideo.objects.get(slug=video_slug)
video.delete()
messages.success(request, (video.title + ' has been deleted.'))
return redirect('course_detail', slug=slug)
# ########################################################
# ########################################################
# Course Registration
# ########################################################
@login_required
@student_required
def course_registration(request):
if request.method == 'POST':
ids = ()
data = request.POST.copy()
data.pop('csrfmiddlewaretoken', None) # remove csrf_token
for key in data.keys():
ids = ids + (str(key),)
for s in range(0, len(ids)):
student = Student.objects.get(student__pk=request.user.id)
course = Course.objects.get(pk=ids[s])
obj = TakenCourse.objects.create(student=student, course=course)
obj.save()
messages.success(request, 'Courses Registered Successfully!')
return redirect('course_registration')
else:
# student = Student.objects.get(student__pk=request.user.id)
student = get_object_or_404(Student, student__id=request.user.id)
taken_courses = TakenCourse.objects.filter(student__student__id=request.user.id)
t = ()
for i in taken_courses:
t += (i.course.pk,)
current_semester = Semester.objects.get(is_current_semester=True)
courses = Course.objects.filter(program__pk=student.department.id, level=student.level, semester=current_semester
).exclude(id__in=t).order_by('year')
all_courses = Course.objects.filter(level=student.level, program__pk=student.department.id)
no_course_is_registered = False # Check if no course is registered
all_courses_are_registered = False
registered_courses = Course.objects.filter(level=student.level).filter(id__in=t)
if registered_courses.count() == 0: # Check if number of registered courses is 0
no_course_is_registered = True
if registered_courses.count() == all_courses.count():
all_courses_are_registered = True
total_first_semester_credit = 0
total_sec_semester_credit = 0
total_registered_credit = 0
for i in courses:
if i.semester == "First":
total_first_semester_credit += int(i.credit)
if i.semester == "Second":
total_sec_semester_credit += int(i.credit)
for i in registered_courses:
total_registered_credit += int(i.credit)
context = {
"all_courses_are_registered": all_courses_are_registered,
"no_course_is_registered": no_course_is_registered,
"current_semester": current_semester,
"courses": courses,
"total_first_semester_credit": total_first_semester_credit,
"total_sec_semester_credit": total_sec_semester_credit,
"registered_courses": registered_courses,
"total_registered_credit": total_registered_credit,
"student": student,
}
return render(request, 'course/course_registration.html', context)
@login_required
@student_required
def course_drop(request):
if request.method == 'POST':
ids = ()
data = request.POST.copy()
data.pop('csrfmiddlewaretoken', None) # remove csrf_token
for key in data.keys():
ids = ids + (str(key),)
for s in range(0, len(ids)):
student = Student.objects.get(student__pk=request.user.id)
course = Course.objects.get(pk=ids[s])
obj = TakenCourse.objects.get(student=student, course=course)
obj.delete()
messages.success(request, 'Successfully Dropped!')
return redirect('course_registration')
# ########################################################
@login_required
def user_course_list(request):
if request.user.is_lecturer:
courses = Course.objects.filter(allocated_course__lecturer__pk=request.user.id)
return render(request, 'course/user_course_list.html', {'courses': courses})
elif request.user.is_student:
student = Student.objects.get(student__pk=request.user.id)
taken_courses = TakenCourse.objects.filter(student__student__id=student.student.id)
courses = Course.objects.filter(level=student.level).filter(program__pk=student.department.id)
return render(request, 'course/user_course_list.html', {
'student': student,
'taken_courses': taken_courses,
'courses': courses
})
else:
return render(request, 'course/user_course_list.html')

21
manage.py Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SMS.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

0
quiz/__init__.py Normal file
View File

74
quiz/admin.py Normal file
View File

@ -0,0 +1,74 @@
from django import forms
from django.contrib import admin
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import ugettext_lazy as _
from .models import Quiz, Progress, Question, MCQuestion, Choice, Essay_Question, Sitting
class ChoiceInline(admin.TabularInline):
model = Choice
class QuizAdminForm(forms.ModelForm):
class Meta:
model = Quiz
exclude = []
questions = forms.ModelMultipleChoiceField(
queryset=Question.objects.all().select_subclasses(),
required=False,
label=_("Questions"),
widget=FilteredSelectMultiple(
verbose_name=_("Questions"),
is_stacked=False))
def __init__(self, *args, **kwargs):
super(QuizAdminForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['questions'].initial = self.instance.question_set.all().select_subclasses()
def save(self, commit=True):
quiz = super(QuizAdminForm, self).save(commit=False)
quiz.save()
quiz.question_set.set(self.cleaned_data['questions'])
self.save_m2m()
return quiz
class QuizAdmin(admin.ModelAdmin):
form = QuizAdminForm
list_display = ('title', )
# list_filter = ('category',)
search_fields = ('description', 'category', )
class MCQuestionAdmin(admin.ModelAdmin):
list_display = ('content', )
# list_filter = ('category',)
fields = ('content', 'figure', 'quiz', 'explanation', 'choice_order')
search_fields = ('content', 'explanation')
filter_horizontal = ('quiz',)
inlines = [ChoiceInline]
class ProgressAdmin(admin.ModelAdmin):
search_fields = ('user', 'score', )
class EssayQuestionAdmin(admin.ModelAdmin):
list_display = ('content', )
# list_filter = ('category',)
fields = ('content', 'quiz', 'explanation', )
search_fields = ('content', 'explanation')
filter_horizontal = ('quiz',)
admin.site.register(Quiz, QuizAdmin)
admin.site.register(MCQuestion, MCQuestionAdmin)
admin.site.register(Progress, ProgressAdmin)
admin.site.register(Essay_Question, EssayQuestionAdmin)
admin.site.register(Sitting)

5
quiz/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class QuizConfig(AppConfig):
name = 'quiz'

63
quiz/forms.py Normal file
View File

@ -0,0 +1,63 @@
from django import forms
from django.forms.widgets import RadioSelect, Textarea
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import ugettext_lazy as _
from django.db import transaction
from django.forms.models import inlineformset_factory
from accounts.models import User
from .models import Question, Quiz, MCQuestion, Choice
class QuestionForm(forms.Form):
def __init__(self, question, *args, **kwargs):
super(QuestionForm, self).__init__(*args, **kwargs)
choice_list = [x for x in question.get_choices_list()]
self.fields["answers"] = forms.ChoiceField(choices=choice_list, widget=RadioSelect)
class EssayForm(forms.Form):
def __init__(self, question, *args, **kwargs):
super(EssayForm, self).__init__(*args, **kwargs)
self.fields["answers"] = forms.CharField(
widget=Textarea(attrs={'style': 'width:100%'}))
class QuizAddForm(forms.ModelForm):
class Meta:
model = Quiz
exclude = []
questions = forms.ModelMultipleChoiceField(
queryset=Question.objects.all().select_subclasses(),
required=False,
label=_("Questions"),
widget=FilteredSelectMultiple(
verbose_name=_("Questions"),
is_stacked=False))
def __init__(self, *args, **kwargs):
super(QuizAddForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['questions'].initial = self.instance.question_set.all().select_subclasses()
def save(self, commit=True):
quiz = super(QuizAddForm, self).save(commit=False)
quiz.save()
quiz.question_set.set(self.cleaned_data['questions'])
self.save_m2m()
return quiz
class MCQuestionForm(forms.ModelForm):
class Meta:
model = MCQuestion
exclude = ()
MCQuestionFormSet = inlineformset_factory(
MCQuestion, Choice, form=MCQuestionForm, fields=['choice', 'correct'], can_delete=True, extra=5
)

View File

@ -0,0 +1,128 @@
# Generated by Django 2.2.3 on 2020-07-29 15:25
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import re
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('course', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Question',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('figure', models.ImageField(blank=True, null=True, upload_to='uploads/%Y/%m/%d', verbose_name='Figure')),
('content', models.CharField(help_text='Enter the question text that you want displayed', max_length=1000, verbose_name='Question')),
('explanation', models.TextField(blank=True, help_text='Explanation to be shown after the question has been answered.', max_length=2000, verbose_name='Explanation')),
],
options={
'verbose_name': 'Question',
'verbose_name_plural': 'Questions',
},
),
migrations.CreateModel(
name='Quiz',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=60, verbose_name='Title')),
('slug', models.SlugField(blank=True, unique=True)),
('description', models.TextField(blank=True, help_text='a description of the quiz', verbose_name='Description')),
('category', models.TextField(blank=True, choices=[('assignment', 'Assignment'), ('exam', 'Exam'), ('practice', 'Practice Quiz')])),
('random_order', models.BooleanField(default=False, help_text='Display the questions in a random order or as they are set?', verbose_name='Random Order')),
('answers_at_end', models.BooleanField(default=False, help_text='Correct answer is NOT shown after question. Answers displayed at the end.', verbose_name='Answers at end')),
('exam_paper', models.BooleanField(default=False, help_text='If yes, the result of each attempt by a user will be stored. Necessary for marking.', verbose_name='Exam Paper')),
('single_attempt', models.BooleanField(default=False, help_text='If yes, only one attempt by a user will be permitted.', verbose_name='Single Attempt')),
('pass_mark', models.SmallIntegerField(blank=True, default=50, help_text='Percentage required to pass exam.', validators=[django.core.validators.MaxValueValidator(100)], verbose_name='Pass Mark')),
('draft', models.BooleanField(blank=True, default=False, help_text='If yes, the quiz is not displayed in the quiz list and can only be taken by users who can edit quizzes.', verbose_name='Draft')),
('timestamp', models.DateTimeField(auto_now=True)),
('course', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='course.Course')),
],
options={
'verbose_name': 'Quiz',
'verbose_name_plural': 'Quizzes',
},
),
migrations.CreateModel(
name='Essay_Question',
fields=[
('question_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='quiz.Question')),
],
options={
'verbose_name': 'Essay style question',
'verbose_name_plural': 'Essay style questions',
},
bases=('quiz.question',),
),
migrations.CreateModel(
name='MCQuestion',
fields=[
('question_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='quiz.Question')),
('choice_order', models.CharField(blank=True, choices=[('content', 'Content'), ('random', 'Random'), ('none', 'None')], help_text='The order in which multichoice choice options are displayed to the user', max_length=30, null=True, verbose_name='Choice Order')),
],
options={
'verbose_name': 'Multiple Choice Question',
'verbose_name_plural': 'Multiple Choice Questions',
},
bases=('quiz.question',),
),
migrations.CreateModel(
name='Sitting',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question_order', models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Question Order')),
('question_list', models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Question List')),
('incorrect_questions', models.CharField(blank=True, max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Incorrect questions')),
('current_score', models.IntegerField(verbose_name='Current Score')),
('complete', models.BooleanField(default=False, verbose_name='Complete')),
('user_answers', models.TextField(blank=True, default='{}', verbose_name='User Answers')),
('start', models.DateTimeField(auto_now_add=True, verbose_name='Start')),
('end', models.DateTimeField(blank=True, null=True, verbose_name='End')),
('course', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='course.Course', verbose_name='Course')),
('quiz', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quiz.Quiz', verbose_name='Quiz')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'permissions': (('view_sittings', 'Can see completed exams.'),),
},
),
migrations.AddField(
model_name='question',
name='quiz',
field=models.ManyToManyField(blank=True, to='quiz.Quiz', verbose_name='Quiz'),
),
migrations.CreateModel(
name='Progress',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('score', models.CharField(max_length=1024, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:,\\d+)*\\Z'), code='invalid', message='Enter only digits separated by commas.')], verbose_name='Score')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'verbose_name': 'User Progress',
'verbose_name_plural': 'User progress records',
},
),
migrations.CreateModel(
name='Choice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('choice', models.CharField(help_text='Enter the choice text that you want displayed', max_length=1000, verbose_name='Content')),
('correct', models.BooleanField(default=False, help_text='Is this a correct answer?', verbose_name='Correct')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='quiz.MCQuestion', verbose_name='Question')),
],
options={
'verbose_name': 'Choice',
'verbose_name_plural': 'Choices',
},
),
]

View File

446
quiz/models.py Normal file
View File

@ -0,0 +1,446 @@
import re
import json
from django.db import models
from django.urls import reverse
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.core.validators import (MaxValueValidator, validate_comma_separated_integer_list,)
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now
from django.conf import settings
from django.db.models.signals import pre_save
from django.db.models import Q
from model_utils.managers import InheritanceManager
from course.models import Course
from .utils import *
CHOICE_ORDER_OPTIONS = (
('content', _('Content')),
('random', _('Random')),
('none', _('None'))
)
CATEGORY_OPTIONS = (
('assignment', _('Assignment')),
('exam', _('Exam')),
('practice', _('Practice Quiz'))
)
class QuizManager(models.Manager):
def search(self, query=None):
qs = self.get_queryset()
if query is not None:
or_lookup = (Q(title__icontains=query) |
Q(description__icontains=query)|
Q(category__icontains=query)|
Q(slug__icontains=query)
)
qs = qs.filter(or_lookup).distinct() # distinct() is often necessary with Q lookups
return qs
class Quiz(models.Model):
course = models.ForeignKey(Course, on_delete=models.CASCADE, null=True)
title = models.CharField(verbose_name=_("Title"), max_length=60, blank=False)
slug = models.SlugField(blank=True, unique=True)
description = models.TextField(verbose_name=_("Description"), blank=True, help_text=_("a description of the quiz"))
category = models.TextField(choices=CATEGORY_OPTIONS, blank=True)
random_order = models.BooleanField(blank=False, default=False, verbose_name=_("Random Order"),
help_text=_("Display the questions in a random order or as they are set?"))
# max_questions = models.PositiveIntegerField(blank=True, null=True, verbose_name=_("Max Questions"),
# help_text=_("Number of questions to be answered on each attempt."))
answers_at_end = models.BooleanField(blank=False, default=False, verbose_name=_("Answers at end"),
help_text=_("Correct answer is NOT shown after question. Answers displayed at the end."))
exam_paper = models.BooleanField(blank=False, default=False, verbose_name=_("Exam Paper"),
help_text=_("If yes, the result of each attempt by a user will be stored. Necessary for marking."))
single_attempt = models.BooleanField(blank=False, default=False, verbose_name=_("Single Attempt"),
help_text=_("If yes, only one attempt by a user will be permitted."))
pass_mark = models.SmallIntegerField(blank=True, default=50, verbose_name=_("Pass Mark"), validators=[MaxValueValidator(100)],
help_text=_("Percentage required to pass exam."))
draft = models.BooleanField(blank=True, default=False, verbose_name=_("Draft"),
help_text=_("If yes, the quiz is not displayed in the quiz list and can only be taken by users who can edit quizzes."))
timestamp = models.DateTimeField(auto_now=True)
objects = QuizManager()
def save(self, force_insert=False, force_update=False, *args, **kwargs):
if self.single_attempt is True:
self.exam_paper = True
if self.pass_mark > 100:
raise ValidationError('%s is above 100' % self.pass_mark)
if self.pass_mark < 0:
raise ValidationError('%s is below 0' % self.pass_mark)
super(Quiz, self).save(force_insert, force_update, *args, **kwargs)
class Meta:
verbose_name = _("Quiz")
verbose_name_plural = _("Quizzes")
def __str__(self):
return self.title
def get_questions(self):
return self.question_set.all().select_subclasses()
@property
def get_max_score(self):
return self.get_questions().count()
def get_absolute_url(self):
# return reverse('quiz_start_page', kwargs={'pk': self.pk})
return reverse('quiz_index', kwargs={'slug': self.course.slug})
def quiz_pre_save_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = unique_slug_generator(instance)
pre_save.connect(quiz_pre_save_receiver, sender=Quiz)
class ProgressManager(models.Manager):
def new_progress(self, user):
new_progress = self.create(user=user, score="")
new_progress.save()
return new_progress
class Progress(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE)
score = models.CharField(max_length=1024, verbose_name=_("Score"), validators=[validate_comma_separated_integer_list])
objects = ProgressManager()
class Meta:
verbose_name = _("User Progress")
verbose_name_plural = _("User progress records")
# @property
def list_all_cat_scores(self):
score_before = self.score
output = {}
if len(self.score) > len(score_before):
# If a new category has been added, save changes.
self.save()
return output
def update_score(self, question, score_to_add=0, possible_to_add=0):
# category_test = Category.objects.filter(category=question.category).exists()
if any([item is False for item in [score_to_add, possible_to_add, isinstance(score_to_add, int), isinstance(possible_to_add, int)]]):
return _("error"), _("category does not exist or invalid score")
to_find = re.escape(str(question.quiz)) + r",(?P<score>\d+),(?P<possible>\d+),"
match = re.search(to_find, self.score, re.IGNORECASE)
if match:
updated_score = int(match.group('score')) + abs(score_to_add)
updated_possible = int(match.group('possible')) + abs(possible_to_add)
new_score = ",".join([str(question.quiz), str(updated_score), str(updated_possible), ""])
# swap old score for the new one
self.score = self.score.replace(match.group(), new_score)
self.save()
else:
# if not present but existing, add with the points passed in
self.score += ",".join([str(question.quiz), str(score_to_add), str(possible_to_add), ""])
self.save()
def show_exams(self):
if self.user.is_superuser:
return Sitting.objects.filter(complete=True).order_by('-end')
else:
return Sitting.objects.filter(user=self.user, complete=True).order_by('-end')
class SittingManager(models.Manager):
def new_sitting(self, user, quiz, course):
if quiz.random_order is True:
question_set = quiz.question_set.all().select_subclasses().order_by('?')
else:
question_set = quiz.question_set.all().select_subclasses()
question_set = [item.id for item in question_set]
if len(question_set) == 0:
raise ImproperlyConfigured('Question set of the quiz is empty. Please configure questions properly')
# if quiz.max_questions and quiz.max_questions < len(question_set):
# question_set = question_set[:quiz.max_questions]
questions = ",".join(map(str, question_set)) + ","
new_sitting = self.create(
user=user, quiz=quiz, course=course, question_order=questions,
question_list=questions, incorrect_questions="",
current_score=0,
complete=False,
user_answers='{}'
)
return new_sitting
def user_sitting(self, user, quiz, course):
if quiz.single_attempt is True and self.filter(user=user, quiz=quiz, course=course, complete=True).exists():
return False
try:
sitting = self.get(user=user, quiz=quiz, course=course, complete=False)
except Sitting.DoesNotExist:
sitting = self.new_sitting(user, quiz, course)
except Sitting.MultipleObjectsReturned:
sitting = self.filter(user=user, quiz=quiz, course=course, complete=False)[0]
return sitting
class Sitting(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_("User"), on_delete=models.CASCADE)
quiz = models.ForeignKey(Quiz, verbose_name=_("Quiz"), on_delete=models.CASCADE)
course = models.ForeignKey(Course, null=True, verbose_name=_("Course"), on_delete=models.CASCADE)
question_order = models.CharField(max_length=1024, verbose_name=_("Question Order"),
validators=[validate_comma_separated_integer_list])
question_list = models.CharField(max_length=1024, verbose_name=_("Question List"),
validators=[validate_comma_separated_integer_list])
incorrect_questions = models.CharField(max_length=1024, blank=True, verbose_name=_("Incorrect questions"),
validators=[validate_comma_separated_integer_list])
current_score = models.IntegerField(verbose_name=_("Current Score"))
complete = models.BooleanField(default=False, blank=False, verbose_name=_("Complete"))
user_answers = models.TextField(blank=True, default='{}', verbose_name=_("User Answers"))
start = models.DateTimeField(auto_now_add=True, verbose_name=_("Start"))
end = models.DateTimeField(null=True, blank=True, verbose_name=_("End"))
objects = SittingManager()
class Meta:
permissions = (("view_sittings", _("Can see completed exams.")),)
def get_first_question(self):
if not self.question_list:
return False
first, _ = self.question_list.split(',', 1)
question_id = int(first)
return Question.objects.get_subclass(id=question_id)
def remove_first_question(self):
if not self.question_list:
return
_, others = self.question_list.split(',', 1)
self.question_list = others
self.save()
def add_to_score(self, points):
self.current_score += int(points)
self.save()
@property
def get_current_score(self):
return self.current_score
def _question_ids(self):
return [int(n) for n in self.question_order.split(',') if n]
@property
def get_percent_correct(self):
dividend = float(self.current_score)
divisor = len(self._question_ids())
if divisor < 1:
return 0 # prevent divide by zero error
if dividend > divisor:
return 100
correct = int(round((dividend / divisor) * 100))
if correct >= 1:
return correct
else:
return 0
def mark_quiz_complete(self):
self.complete = True
self.end = now()
self.save()
def add_incorrect_question(self, question):
if len(self.incorrect_questions) > 0:
self.incorrect_questions += ','
self.incorrect_questions += str(question.id) + ","
if self.complete:
self.add_to_score(-1)
self.save()
@property
def get_incorrect_questions(self):
return [int(q) for q in self.incorrect_questions.split(',') if q]
def remove_incorrect_question(self, question):
current = self.get_incorrect_questions
current.remove(question.id)
self.incorrect_questions = ','.join(map(str, current))
self.add_to_score(1)
self.save()
@property
def check_if_passed(self):
return self.get_percent_correct >= self.quiz.pass_mark
@property
def result_message(self):
if self.check_if_passed:
return f"You have passed this quiz, congratulation"
else:
return f"You failed this quiz, don't give up! try until you have passed."
def add_user_answer(self, question, guess):
current = json.loads(self.user_answers)
current[question.id] = guess
self.user_answers = json.dumps(current)
self.save()
def get_questions(self, with_answers=False):
question_ids = self._question_ids()
questions = sorted(self.quiz.question_set.filter(id__in=question_ids).select_subclasses(), key=lambda q: question_ids.index(q.id))
if with_answers:
user_answers = json.loads(self.user_answers)
for question in questions:
question.user_answer = user_answers[str(question.id)]
return questions
@property
def questions_with_user_answers(self):
return {q: q.user_answer for q in self.get_questions(with_answers=True)}
@property
def get_max_score(self):
return len(self._question_ids())
def progress(self):
answered = len(json.loads(self.user_answers))
total = self.get_max_score
return answered, total
class Question(models.Model):
quiz = models.ManyToManyField(Quiz, verbose_name=_("Quiz"), blank=True)
figure = models.ImageField(upload_to='uploads/%Y/%m/%d', blank=True, null=True, verbose_name=_("Figure"))
content = models.CharField(max_length=1000, blank=False,
help_text=_("Enter the question text that you want displayed"), verbose_name=_('Question'))
explanation = models.TextField(max_length=2000, blank=True,
help_text=_("Explanation to be shown after the question has been answered."),
verbose_name=_('Explanation'))
objects = InheritanceManager()
class Meta:
verbose_name = _("Question")
verbose_name_plural = _("Questions")
def __str__(self):
return self.content
class MCQuestion(Question):
choice_order = models.CharField(
max_length=30, null=True, blank=True,
choices=CHOICE_ORDER_OPTIONS,
help_text=_("The order in which multichoice choice options are displayed to the user"),
verbose_name=_("Choice Order"))
def check_if_correct(self, guess):
answer = Choice.objects.get(id=guess)
if answer.correct is True:
return True
else:
return False
def order_choices(self, queryset):
if self.choice_order == 'content':
return queryset.order_by('choice')
if self.choice_order == 'random':
return queryset.order_by('?')
if self.choice_order == 'none':
return queryset.order_by()
return queryset
def get_choices(self):
return self.order_choices(Choice.objects.filter(question=self))
def get_choices_list(self):
return [(choice.id, choice.choice) for choice in
self.order_choices(Choice.objects.filter(question=self))]
def answer_choice_to_string(self, guess):
return Choice.objects.get(id=guess).choice
class Meta:
verbose_name = _("Multiple Choice Question")
verbose_name_plural = _("Multiple Choice Questions")
class Choice(models.Model):
question = models.ForeignKey(MCQuestion, verbose_name=_("Question"), on_delete=models.CASCADE)
choice = models.CharField(max_length=1000, blank=False,
help_text=_("Enter the choice text that you want displayed"),
verbose_name=_("Content"))
correct = models.BooleanField(blank=False, default=False,
help_text=_("Is this a correct answer?"),
verbose_name=_("Correct"))
def __str__(self):
return self.choice
class Meta:
verbose_name = _("Choice")
verbose_name_plural = _("Choices")
class Essay_Question(Question):
def check_if_correct(self, guess):
return False
def get_answers(self):
return False
def get_answers_list(self):
return False
def answer_choice_to_string(self, guess):
return str(guess)
def __str__(self):
return self.content
class Meta:
verbose_name = _("Essay style question")
verbose_name_plural = _("Essay style questions")

View File

@ -0,0 +1,32 @@
{% load i18n %}
{% if previous.answers %}
{% if user_was_incorrect %}
<div class="alert alert-error">
<strong>{% trans "You answered the above question incorrectly" %}</strong>
</div>
{% endif %}
<table class="table table-striped table-bordered">
<tbody>
{% for answer in previous.answers %}
{% if answer.correct %}
<tr class="success">
<td>{{ answer }}</td>
<td><strong>{% trans "This is the correct answer" %}</strong></td>
{% else %}
<tr>
<td>{{ answer }}</td>
<td>
{% if previous.question_type.MCQuestion %}
{% if answer.id|add:"0" == previous.previous_answer|add:"0" %}
{% trans "This was your answer." %}
{% endif %}
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}

View File

@ -0,0 +1,92 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %} {% trans "Progress Page" %} {% endblock %}
{% block description %} {% trans "User Progress Page" %} {% endblock %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i> Progress Page</div>
{% if cat_scores %}
<div class="header-title text-center">{% trans "Question Category Scores" %}</div>
<div class="title-line"></div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>{% trans "Category" %}</th>
<th>{% trans "Correctly answererd" %}</th>
<th>{% trans "Incorrect" %}</th>
<th>%</th>
</tr>
</thead>
<tbody>
{% for cat, value in cat_scores.items %}
<tr>
<td>{{ cat }}</td>
<td>{{ value.0 }}</td>
<td>{{ value.1 }}</td>
<td>{{ value.2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% if exams %}
<hr>
<div class="header-title-xl">{% trans "Previous exam papers" %}</div>
<p class="lead text-muted">
{% trans "Below are the results of exams that you have sat." %}
</p>
<div class="info-text bg-danger mb-2">Total complete exams: {{ exams_counter }}</div>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>#</th>
<th>{% trans "Quiz Title" %}</th>
<th>{% trans "Score" %}</th>
<th>{% trans "Possible Score" %}</th>
<th>Out of 100%</th>
</tr>
</thead>
<tbody>
{% for exam in exams %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ exam.quiz.title }}</td>
<td>{{ exam.current_score }}</td>
<td>{{ exam.get_max_score }}</td>
<td>{{ exam.get_percent_correct }}%</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if not cat_scores and not exams %}
<div class="col-12 p-4 text-center"><h3><i class="far fa-frown"></i></h3> No recordes yet. to get a good recorde, try to do some quizzes in your course.</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,169 @@
{% extends "base.html" %}
{% load i18n%}
{% block title %} {{ quiz.title }} {% endblock %}
{% block description %} {{ quiz.title }} - {{ quiz.description }} {% endblock %}
{% block content %}
<div id="input-nav"><a href="{% url 'home' %}" class="primary1">Home</a> <i>&rsaquo;</i>
<a href="{% url 'programs' %}" class="primary1">Programs </a> <i>&rsaquo;</i>
<a href="{% url 'program_detail' course.program.id %}" class="primary1">{{ course.program }} </a> <i>&rsaquo;</i>
<a href="{{ course.get_absolute_url }}" class="primary1">{{ course }} </a> <i>&rsaquo;</i>
<a href="{% url 'quiz_index' course.slug %}" class="primary1">Quizzes</a> <i>&rsaquo;</i> {{ quiz.title|title }}</div>
<div class="container">
<div class="header-title-xl text-center">{{ quiz.title|title }}</div>
<div class="title-line mb-2"></div>
{% if previous.answers %}
<p class="muted"><small>{% trans "The previous question" %}:</small></p>
<p>{{ previous.previous_question }}</p>
{% if previous.previous_outcome %}
<div class="alert alert-success">
{% else %}
<div class="alert alert-warning">
{% endif %}
<p><small>
{% trans "Your answer was" %} </small>
<strong>
{{ previous.previous_outcome|yesno:"correct,incorrect" }}
</strong>
</p>
</div>
{% load i18n %}
{% if previous.answers %}
{% if user_was_incorrect %}
<div class="alert alert-error">
<strong>{% trans "You answered the above question incorrectly" %}</strong>
</div>
{% endif %}
<table class="table table-striped table-bordered">
<tbody>
{% for answer in previous.answers %}
{% if answer.correct %}
<tr class="success">
<td>{{ answer }}</td>
<td><strong>{% trans "This is the correct answer" %}</strong></td>
{% else %}
<tr>
<td>{{ answer }}</td>
<td>
{% if previous.question_type.MCQuestion %}
{% if answer.id|add:"0" == previous.previous_answer|add:"0" %}
{% trans "This was your answer." %}
{% endif %}
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<p><strong>{% trans "Explanation" %}:</strong></p>
<p class="p-2" style="background-color: #fcf8e3;">
{% if previous.previous_question.explanation %}
{{ previous.previous_question.explanation }}
{% else %}
{% trans "No explanation set to this question." %}
{% endif %}
</p>
<!-- <p><strong>{% trans "Explanation" %}:</strong></p>
<div class="well " style="background-color: #fcf8e3;">
<p>{{ previous.previous_question.explanation }}</p>
</div> -->
<hr>
{% endif %}
<br />
{% if question %}
{% if progress.0|add:1 == 1 %}
<div id="popup-box-messages">
<div class="box-messages">
<a id="popup-btn-messages" class="btn btn-light"><i class="fas fa-times"></i></a>
<div class="lead">Quiz instractions</div>
<div class="alert alert-warning">
<p>
You can go back to the previous question after you submit it or check it,
so make sure your answer, before you submit it!
</p>
</div>
<button id="popup-btn-messages" type="button" class="btn btn-primary" data-dismiss="modal">Understood</button>
</div>
</div>
{% endif %}
{% if progress %}
<div class="info-text bg-danger" style="float: right;">
{% trans "Question" %} {{ progress.0|add:1 }} {% trans "of" %} {{ progress.1 }}
</div>
{% endif %}
<p>
<small class="muted">{% trans "Quiz category" %}:</small>
<strong>{{ quiz.category }}</strong>
</p>
<div class="card">
<div class="card-title lead bg-light p-2">{{ question.content }}</div>
{% if question.figure %}
<div class="col-md-8 mx-auto">
<img class="q-img" src="{{ question.figure.url }}" alt="{{ question.content }}" style="max-width: 100%;"/>
</div>
{% endif %}
<div class="card-subtitle p-4">
<form action="" method="POST">{% csrf_token %}
<input type=hidden name="question_id" value="{{ question.id }}">
<ul class="list-group">
{% for answer in form.answers %}
<li class="list-group-item">
{{ answer }}
</li>
{% endfor %}
</ul>
<input type="submit" value={% trans "Check" %} class="btn btn-large btn-block btn-primary" >
<!-- <input type="submit" value={% trans "Previous" %} class="btn btn-large btn-block btn-outline-primary" > -->
</form>
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block js %}
<script>
const popupMessagesButtons = document.querySelectorAll('#popup-btn-messages');
for (const button of popupMessagesButtons) {
button.addEventListener('click', () => {
document.getElementById('popup-box-messages').style.display = 'none';
})
}
</script>
{% endblock js %}

Some files were not shown because too many files have changed in this diff Show More