initial commit
This commit is contained in:
parent
30af5f4735
commit
f6ab9e8128
93
.gitignore
vendored
Normal file
93
.gitignore
vendored
Normal 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
68
README.md
Normal 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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
After the student finished the quiz, here is how the result display
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# 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
0
SMS/__init__.py
Normal file
159
SMS/settings.py
Normal file
159
SMS/settings.py
Normal 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
27
SMS/urls.py
Normal 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
16
SMS/wsgi.py
Normal 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
0
accounts/__init__.py
Normal file
28
accounts/admin.py
Normal file
28
accounts/admin.py
Normal 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
5
accounts/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AccountsConfig(AppConfig):
|
||||||
|
name = 'accounts'
|
||||||
48
accounts/decorators.py
Normal file
48
accounts/decorators.py
Normal 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
322
accounts/forms.py
Normal 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
|
||||||
53
accounts/migrations/0001_initial.py
Normal file
53
accounts/migrations/0001_initial.py
Normal 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
39
accounts/migrations/0002_auto_20200729_1825.py
Normal file
39
accounts/migrations/0002_auto_20200729_1825.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
accounts/migrations/0003_auto_20200730_0740.py
Normal file
18
accounts/migrations/0003_auto_20200730_0740.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
accounts/migrations/0004_auto_20200822_2238.py
Normal file
18
accounts/migrations/0004_auto_20200822_2238.py
Normal 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/'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
accounts/migrations/0005_auto_20200822_2246.py
Normal file
18
accounts/migrations/0005_auto_20200822_2246.py
Normal 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/'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
accounts/migrations/0006_auto_20200822_2308.py
Normal file
18
accounts/migrations/0006_auto_20200822_2308.py
Normal 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/'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
accounts/migrations/0007_auto_20200825_1248.py
Normal file
18
accounts/migrations/0007_auto_20200825_1248.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
38
accounts/migrations/0008_auto_20200831_1315.py
Normal file
38
accounts/migrations/0008_auto_20200831_1315.py
Normal 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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
27
accounts/migrations/0009_auto_20200906_1403.py
Normal file
27
accounts/migrations/0009_auto_20200906_1403.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
||||||
0
accounts/migrations/__init__.py
Normal file
0
accounts/migrations/__init__.py
Normal file
161
accounts/models.py
Normal file
161
accounts/models.py
Normal 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
|
||||||
87
accounts/templates/accounts/add_staff.html
Normal file
87
accounts/templates/accounts/add_staff.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'lecturer_list' %}" class="primary1">Lecturers</a> <i>›</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 %}
|
||||||
110
accounts/templates/accounts/add_student.html
Normal file
110
accounts/templates/accounts/add_student.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'student_list' %}" class="primary1">Students</a> <i>›</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 %}
|
||||||
72
accounts/templates/accounts/edit_lecturer.html
Normal file
72
accounts/templates/accounts/edit_lecturer.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'lecturer_list' %}" class="primary1">Lecturers </a><i>›</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 %}
|
||||||
73
accounts/templates/accounts/edit_student.html
Normal file
73
accounts/templates/accounts/edit_student.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'student_list' %}" class="primary1">Students </a><i>›</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 %}
|
||||||
119
accounts/templates/accounts/lecturer_list.html
Normal file
119
accounts/templates/accounts/lecturer_list.html
Normal 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>›</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 %}
|
||||||
13
accounts/templates/accounts/parent_form.html
Normal file
13
accounts/templates/accounts/parent_form.html
Normal 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 %}
|
||||||
129
accounts/templates/accounts/profile.html
Normal file
129
accounts/templates/accounts/profile.html
Normal 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>›</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 %}
|
||||||
127
accounts/templates/accounts/profile_single.html
Normal file
127
accounts/templates/accounts/profile_single.html
Normal 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>›</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 %}
|
||||||
110
accounts/templates/accounts/student_list.html
Normal file
110
accounts/templates/accounts/student_list.html
Normal 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>›</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 %}
|
||||||
86
accounts/templates/registration/login.html
Normal file
86
accounts/templates/registration/login.html
Normal 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 %}
|
||||||
19
accounts/templates/registration/password_reset.html
Normal file
19
accounts/templates/registration/password_reset.html
Normal 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 %}
|
||||||
15
accounts/templates/registration/password_reset_complete.html
Normal file
15
accounts/templates/registration/password_reset_complete.html
Normal 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 %}
|
||||||
34
accounts/templates/registration/password_reset_confirm.html
Normal file
34
accounts/templates/registration/password_reset_confirm.html
Normal 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 %}
|
||||||
16
accounts/templates/registration/password_reset_done.html
Normal file
16
accounts/templates/registration/password_reset_done.html
Normal 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 %}
|
||||||
24
accounts/templates/registration/registration_base.html
Normal file
24
accounts/templates/registration/registration_base.html
Normal 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>
|
||||||
62
accounts/templates/setting/admin_panel.html
Normal file
62
accounts/templates/setting/admin_panel.html
Normal 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>›</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 »</a>
|
||||||
|
<p><span>⟶</span> CRUD (Create, Retriev, Update & Delete) lecturers</p><hr>
|
||||||
|
Manage<a class="btn btn-sm btn-secondary" href="{% url 'student_list' %}"> Students »</a>
|
||||||
|
<p><span>⟶</span> CRUD (Create, Retriev, Update & Delete) students</p><hr>
|
||||||
|
Manage<a class="btn btn-sm btn-secondary" href="{% url 'session_list' %}"> Session »</a>
|
||||||
|
<p><span>⟶</span> CRUD (Create, Retriev, Update & Delete) sessions</p><hr>
|
||||||
|
Manage<a class="btn btn-sm btn-secondary" href="{% url 'semester_list' %}"> Semester »</a>
|
||||||
|
<p><span>⟶</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>⟶</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 »</a>
|
||||||
|
<p><span>⟶</span> CRUD (Create, Retriev, Update & Delete) programs</p>
|
||||||
|
<p><span>⟶</span> CRUD (Create, Retriev, Update & Delete) courses</p><hr>
|
||||||
|
Manage<a class="btn btn-sm btn-secondary" href="{% url 'course_allocation_view' %}"> Course Allocations »</a>
|
||||||
|
<p><span>⟶</span> CRUD (Create, Retriev, Update & Delete) course allocations</p><hr>
|
||||||
|
Manage<a class="btn btn-sm btn-secondary" href="{% url 'home' %}"> News & Events »</a>
|
||||||
|
<p><span>⟶</span> CRUD (Create, Retriev, Update & Delete) News & Events</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
60
accounts/templates/setting/password_change.html
Normal file
60
accounts/templates/setting/password_change.html
Normal 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>›</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 %}
|
||||||
72
accounts/templates/setting/profile_info_change.html
Normal file
72
accounts/templates/setting/profile_info_change.html
Normal 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>›</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
3
accounts/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
68
accounts/urls.py
Normal file
68
accounts/urls.py
Normal 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
15
accounts/validators.py
Normal 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
320
accounts/views.py
Normal 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
0
app/__init__.py
Normal file
9
app/admin.py
Normal file
9
app/admin.py
Normal 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
5
app/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AppConfig(AppConfig):
|
||||||
|
name = 'app'
|
||||||
74
app/forms.py
Normal file
74
app/forms.py
Normal 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']
|
||||||
45
app/migrations/0001_initial.py
Normal file
45
app/migrations/0001_initial.py
Normal 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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
18
app/migrations/0002_auto_20200730_0746.py
Normal file
18
app/migrations/0002_auto_20200730_0746.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
app/migrations/0003_auto_20200730_0756.py
Normal file
18
app/migrations/0003_auto_20200730_0756.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
app/migrations/__init__.py
Normal file
0
app/migrations/__init__.py
Normal file
82
app/models.py
Normal file
82
app/models.py
Normal 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
|
||||||
90
app/templates/app/index.html
Normal file
90
app/templates/app/index.html
Normal 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 %}
|
||||||
39
app/templates/app/post_add.html
Normal file
39
app/templates/app/post_add.html
Normal 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>›</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 %}
|
||||||
91
app/templates/app/semester_list.html
Normal file
91
app/templates/app/semester_list.html
Normal 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>›</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 %}
|
||||||
39
app/templates/app/semester_update.html
Normal file
39
app/templates/app/semester_update.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'semester_list' %}" class="primary1">Semester List</a> <i>›</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 %}
|
||||||
90
app/templates/app/session_list.html
Normal file
90
app/templates/app/session_list.html
Normal 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>›</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 %}
|
||||||
39
app/templates/app/session_update.html
Normal file
39
app/templates/app/session_update.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'session_list' %}" class="primary1">Session List</a> <i>›</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 %}
|
||||||
10
app/templates/common/404.html
Normal file
10
app/templates/common/404.html
Normal 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
3
app/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
27
app/urls.py
Normal file
27
app/urls.py
Normal 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
292
app/views.py
Normal 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
0
course/__init__.py
Normal file
10
course/admin.py
Normal file
10
course/admin.py
Normal 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
5
course/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CourseConfig(AppConfig):
|
||||||
|
name = 'course'
|
||||||
105
course/forms.py
Normal file
105
course/forms.py
Normal 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'})
|
||||||
67
course/migrations/0001_initial.py
Normal file
67
course/migrations/0001_initial.py
Normal 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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
27
course/migrations/0002_uploadvideo.py
Normal file
27
course/migrations/0002_uploadvideo.py
Normal 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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
19
course/migrations/0003_auto_20200803_1335.py
Normal file
19
course/migrations/0003_auto_20200803_1335.py
Normal 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'])]),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
course/migrations/0004_auto_20200822_2238.py
Normal file
18
course/migrations/0004_auto_20200822_2238.py
Normal 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),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
course/migrations/__init__.py
Normal file
0
course/migrations/__init__.py
Normal file
184
course/models.py
Normal file
184
course/models.py
Normal 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)
|
||||||
88
course/templates/course/course_add.html
Normal file
88
course/templates/course/course_add.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'programs' %}" class="primary1">Programs</a> <i>›</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 %}
|
||||||
51
course/templates/course/course_allocation_form.html
Normal file
51
course/templates/course/course_allocation_form.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'course_allocation_view' %}" class="primary1">Allocations</a> <i>›</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 %}
|
||||||
92
course/templates/course/course_allocation_view.html
Normal file
92
course/templates/course/course_allocation_view.html
Normal 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>›</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 %}
|
||||||
272
course/templates/course/course_registration.html
Normal file
272
course/templates/course/course_registration.html
Normal 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>›</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 %}
|
||||||
289
course/templates/course/course_single.html
Normal file
289
course/templates/course/course_single.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'programs' %}" class="primary1">Programs </a> <i>›</i>
|
||||||
|
<a href="{% url 'program_detail' course.program.id %}" class="primary1"> {{ course.program }}</a> <i>›</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 »</a></p> -->
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
48
course/templates/course/program_add.html
Normal file
48
course/templates/course/program_add.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'programs' %}" class="primary1">Programs</a> <i>›</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 %}
|
||||||
115
course/templates/course/program_list.html
Normal file
115
course/templates/course/program_list.html
Normal 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>›</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 %}
|
||||||
111
course/templates/course/program_single.html
Normal file
111
course/templates/course/program_single.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'programs' %}" class="primary1">Programs</a> <i>›</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">«</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 }}">»</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
136
course/templates/course/user_course_list.html
Normal file
136
course/templates/course/user_course_list.html
Normal 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>›</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">«</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 }}">»</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
63
course/templates/upload/upload_file_form.html
Normal file
63
course/templates/upload/upload_file_form.html
Normal 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>›</i> <a href="{% url 'programs' %}" class="primary1">Programs</a> <i>›</i>
|
||||||
|
<a href="{% url 'program_detail' course.program.id %}" class="primary1"> {{ course.program }}</a> <i>›</i>
|
||||||
|
<a href="{% url 'course_detail' course.slug %}" class="primary1">{{ course }}</a>
|
||||||
|
<i>›</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 %}
|
||||||
63
course/templates/upload/upload_video_form.html
Normal file
63
course/templates/upload/upload_video_form.html
Normal 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>›</i> <a href="{% url 'programs' %}" class="primary1">Programs</a> <i>›</i>
|
||||||
|
<a href="{% url 'program_detail' course.program.id %}" class="primary1"> {{ course.program }}</a> <i>›</i>
|
||||||
|
<a href="{% url 'course_detail' course.slug %}" class="primary1">{{ course }}</a>
|
||||||
|
<i>›</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 %}
|
||||||
33
course/templates/upload/video_single.html
Normal file
33
course/templates/upload/video_single.html
Normal 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>›</i> <a href="{% url 'programs' %}" class="primary1">Programs</a> <i>›</i>
|
||||||
|
<a href="{% url 'program_detail' video.course.program.id %}" class="primary1"> {{ video.course.program }}</a> <i>›</i>
|
||||||
|
<a href="{% url 'course_detail' video.course.slug %}" class="primary1">{{ video.course }}</a>
|
||||||
|
<i>›</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
3
course/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
53
course/urls.py
Normal file
53
course/urls.py
Normal 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
31
course/utils.py
Normal 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
475
course/views.py
Normal 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
21
manage.py
Normal 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
0
quiz/__init__.py
Normal file
74
quiz/admin.py
Normal file
74
quiz/admin.py
Normal 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
5
quiz/apps.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class QuizConfig(AppConfig):
|
||||||
|
name = 'quiz'
|
||||||
63
quiz/forms.py
Normal file
63
quiz/forms.py
Normal 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
|
||||||
|
)
|
||||||
128
quiz/migrations/0001_initial.py
Normal file
128
quiz/migrations/0001_initial.py
Normal 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',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
quiz/migrations/__init__.py
Normal file
0
quiz/migrations/__init__.py
Normal file
446
quiz/models.py
Normal file
446
quiz/models.py
Normal 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")
|
||||||
32
quiz/templates/correct_answer.html
Normal file
32
quiz/templates/correct_answer.html
Normal 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 %}
|
||||||
92
quiz/templates/progress.html
Normal file
92
quiz/templates/progress.html
Normal 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>›</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 %}
|
||||||
169
quiz/templates/question.html
Normal file
169
quiz/templates/question.html
Normal 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>›</i>
|
||||||
|
<a href="{% url 'programs' %}" class="primary1">Programs </a> <i>›</i>
|
||||||
|
<a href="{% url 'program_detail' course.program.id %}" class="primary1">{{ course.program }} </a> <i>›</i>
|
||||||
|
<a href="{{ course.get_absolute_url }}" class="primary1">{{ course }} </a> <i>›</i>
|
||||||
|
<a href="{% url 'quiz_index' course.slug %}" class="primary1">Quizzes</a> <i>›</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
Loading…
x
Reference in New Issue
Block a user