├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── api ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py └── views.py ├── courses ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── management │ └── commands │ │ └── filldb.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_course_students.py │ ├── 0003_auto_20180216_1327.py │ └── __init__.py ├── models.py ├── serializers.py ├── templates │ └── courses │ │ ├── course_detail.html │ │ ├── course_list.html │ │ ├── do_section.html │ │ ├── do_test.html │ │ └── show_results.html ├── tests.py └── views.py ├── elearning ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── manage.py ├── requirements.txt ├── static ├── css │ ├── app.css │ └── bootstrap.min.css └── js │ ├── bootstrap.min.js │ ├── jquery-3.4.1.min.js │ └── popper.min.js ├── students ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20200202_2118.py │ └── __init__.py ├── models.py ├── templates │ └── students │ │ ├── signup.html │ │ └── student_detail.html ├── tests.py └── views.py └── templates ├── base.html ├── includes └── form.html └── registration ├── logged_out.html └── login.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | ### Python ### 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | 8 | ### Django ### 9 | *.log 10 | *.pot 11 | *.pyc 12 | __pycache__/ 13 | local_settings.py 14 | 15 | .env 16 | db.sqlite3 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Agnieszka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn elearning.wsgi --log-file - -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Python Version](https://img.shields.io/badge/python-3.7-brightgreen.svg)](https://python.org) 2 | [![Django Version](https://img.shields.io/badge/django-3.0-brightgreen.svg)](https://djangoproject.com) 3 | [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 4 | 5 | # E-learning portal 6 | Project based on udemy course [Django Projects: E-Learning Portal](https://www.udemy.com/course/django-projects-e-learning-portal) 7 | Project was created for learning purpose only, without intention to develop it further 8 | 9 | ## LICENSE 10 | The source code is released under the [MIT License](LICENSE). 11 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aga-Ma/e-learning-portal/b565ded17947b54e96c73d249d6f9ad7a936ec7d/api/__init__.py -------------------------------------------------------------------------------- /api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | name = 'api' 6 | -------------------------------------------------------------------------------- /api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aga-Ma/e-learning-portal/b565ded17947b54e96c73d249d6f9ad7a936ec7d/api/migrations/__init__.py -------------------------------------------------------------------------------- /api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from students.models import User 4 | 5 | 6 | class UserSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = User 9 | fields = ['username', 'email', ] -------------------------------------------------------------------------------- /api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | 3 | from students.models import User 4 | from api.serializers import UserSerializer 5 | 6 | 7 | class UserViewSet(viewsets.ModelViewSet): 8 | queryset = User.objects.order_by('-date_joined') 9 | serializer_class = UserSerializer 10 | -------------------------------------------------------------------------------- /courses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aga-Ma/e-learning-portal/b565ded17947b54e96c73d249d6f9ad7a936ec7d/courses/__init__.py -------------------------------------------------------------------------------- /courses/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from courses.models import Course, Section, Question, Answer 4 | 5 | 6 | class CourseAdmin(admin.ModelAdmin): 7 | pass 8 | 9 | 10 | admin.site.register(Course, CourseAdmin) 11 | 12 | 13 | class SectionAdmin(admin.ModelAdmin): 14 | pass 15 | 16 | 17 | admin.site.register(Section, SectionAdmin) 18 | 19 | 20 | class QuestionAdmin(admin.ModelAdmin): 21 | pass 22 | 23 | 24 | admin.site.register(Question, QuestionAdmin) 25 | 26 | 27 | class AnswerAdmin(admin.ModelAdmin): 28 | pass 29 | 30 | 31 | admin.site.register(Answer, AnswerAdmin) 32 | -------------------------------------------------------------------------------- /courses/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoursesConfig(AppConfig): 5 | name = 'courses' 6 | -------------------------------------------------------------------------------- /courses/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | from courses.models import Course 4 | 5 | 6 | class CourseForm(forms.ModelForm): 7 | 8 | class Meta: 9 | model = Course 10 | fields = ['name', ] -------------------------------------------------------------------------------- /courses/management/commands/filldb.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from courses import models 4 | 5 | 6 | class Command(BaseCommand): 7 | 8 | def handle(self, *args, **options): 9 | course1, _ = models.Course.objects.get_or_create(name='Simple Math') 10 | section, _ = models.Section.objects.get_or_create(title='Addition', number=1, test='simple addition test', 11 | course=course1) 12 | question, _ = models.Question.objects.get_or_create(text='2+2', section=section) 13 | answer, _ = models.Answer.objects.get_or_create(text='4', correct=True, question=question) 14 | answer2, _ = models.Answer.objects.get_or_create(text='5', correct=False, question=question) 15 | -------------------------------------------------------------------------------- /courses/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2017-11-02 19:54 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Course', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.CharField(max_length=200)), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /courses/migrations/0002_course_students.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2018-02-16 12:13 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('courses', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='course', 19 | name='students', 20 | field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /courses/migrations/0003_auto_20180216_1327.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.7 on 2018-02-16 13:27 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ('courses', '0002_course_students'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Answer', 20 | fields=[ 21 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 22 | ('text', models.CharField(max_length=1000)), 23 | ('correct', models.BooleanField()), 24 | ], 25 | ), 26 | migrations.CreateModel( 27 | name='Question', 28 | fields=[ 29 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 30 | ('text', models.CharField(max_length=1000)), 31 | ], 32 | ), 33 | migrations.CreateModel( 34 | name='Section', 35 | fields=[ 36 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 37 | ('title', models.CharField(max_length=100)), 38 | ('number', models.IntegerField()), 39 | ('test', models.TextField()), 40 | ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Course')), 41 | ], 42 | ), 43 | migrations.CreateModel( 44 | name='UserAnswer', 45 | fields=[ 46 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 47 | ('answer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Answer')), 48 | ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Question')), 49 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 50 | ], 51 | ), 52 | migrations.AddField( 53 | model_name='question', 54 | name='section', 55 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Section'), 56 | ), 57 | migrations.AddField( 58 | model_name='answer', 59 | name='question', 60 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='courses.Question'), 61 | ), 62 | migrations.AlterUniqueTogether( 63 | name='useranswer', 64 | unique_together=set([('question', 'user')]), 65 | ), 66 | migrations.AlterUniqueTogether( 67 | name='section', 68 | unique_together=set([('course', 'number')]), 69 | ), 70 | ] 71 | -------------------------------------------------------------------------------- /courses/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aga-Ma/e-learning-portal/b565ded17947b54e96c73d249d6f9ad7a936ec7d/courses/migrations/__init__.py -------------------------------------------------------------------------------- /courses/models.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from django.conf import settings 3 | from django.db import models 4 | 5 | from students.models import User 6 | 7 | 8 | class Course(models.Model): 9 | name = models.CharField(max_length=200) 10 | students = models.ManyToManyField(User) 11 | 12 | def get_absolute_url(self): 13 | return reverse('course_detail', args=(self.id,)) 14 | 15 | def __str__(self): 16 | return self.name 17 | 18 | 19 | class Section(models.Model): 20 | course = models.ForeignKey(Course, on_delete=models.CASCADE) 21 | title = models.CharField(max_length=100) 22 | number = models.IntegerField() 23 | test = models.TextField() 24 | 25 | class Meta: 26 | unique_together = ('course', 'number', ) 27 | 28 | def __str__(self): 29 | return self.title 30 | 31 | def get_test_url(self): 32 | return reverse('do_test', args=(self.id,)) 33 | 34 | def get_absolute_url(self): 35 | return reverse('do_test', args=(self.id,)) 36 | 37 | def get_next_section_url(self): 38 | next_section = Section.objects.get(number=self.number+1) 39 | return reverse('do_section', args=(next_section.id,)) 40 | 41 | 42 | class Question(models.Model): 43 | section = models.ForeignKey(Section, on_delete=models.CASCADE) 44 | text = models.CharField(max_length=1000) 45 | 46 | def __str__(self): 47 | return self.text 48 | 49 | 50 | class Answer(models.Model): 51 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 52 | text = models.CharField(max_length=1000) 53 | correct = models.BooleanField() 54 | 55 | def __str__(self): 56 | return self.text 57 | 58 | 59 | class UserAnswer(models.Model): 60 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 61 | answer = models.ForeignKey(Answer, on_delete=models.CASCADE) 62 | user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) 63 | 64 | class Meta: 65 | unique_together = ('question', 'user', ) 66 | -------------------------------------------------------------------------------- /courses/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from courses.models import Section 4 | 5 | 6 | class SectionSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = Section 9 | fields = '__all__' 10 | -------------------------------------------------------------------------------- /courses/templates/courses/course_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block breadcrumb %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
    12 | {% for section in course.section_set.all %} 13 |
  • {{ section }}
  • 14 | {% endfor %} 15 |
16 |
17 |
18 |
19 | {% endblock %} -------------------------------------------------------------------------------- /courses/templates/courses/course_list.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block breadcrumb %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |
    12 | {% for course in course_list %} 13 |
  1. {{ course.name }}
  2. 14 |
      15 | {% for student in course.students.all %} 16 |
    • {{ student }}
    • 17 | {% endfor %} 18 |
    19 | {% endfor %} 20 |
21 |
22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /courses/templates/courses/do_section.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 |

Section {{ section.number }}: {{ section.title }}

5 | 6 |

7 | {{ section.text }} 8 |

9 | 10 |

Take the test

11 | {% endblock %} -------------------------------------------------------------------------------- /courses/templates/courses/do_test.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block breadcrumb %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 | {% csrf_token %} 10 | {% for question in section.question_set.all %} 11 |

{{ question.text }}

12 | 19 | {% endfor %} 20 | 21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /courses/templates/courses/show_results.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block breadcrumb %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 | 9 |

You scored {{ score }}%

10 | 11 | 15 | {% endblock %} -------------------------------------------------------------------------------- /courses/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /courses/views.py: -------------------------------------------------------------------------------- 1 | from django.core.exceptions import PermissionDenied, SuspiciousOperation 2 | from django.urls import reverse 3 | from django.db import transaction 4 | from django.http import HttpResponseRedirect 5 | from django.views.generic import DetailView, CreateView, ListView 6 | from django.shortcuts import render, redirect 7 | 8 | from courses.models import Course, UserAnswer, Section, Question 9 | from courses.forms import CourseForm 10 | from courses.serializers import SectionSerializer 11 | from rest_framework import viewsets 12 | from rest_framework.decorators import action 13 | from rest_framework.response import Response 14 | 15 | 16 | class CourseDetailView(DetailView): 17 | model = Course 18 | 19 | 20 | course_detail = CourseDetailView.as_view() 21 | 22 | 23 | class CourseListView(ListView): 24 | model = Course 25 | queryset = Course.objects.prefetch_related('students') 26 | 27 | 28 | course_list = CourseListView.as_view() 29 | 30 | 31 | class CourseAddView(CreateView): 32 | model = Course 33 | fields = '__all__' 34 | 35 | 36 | course_add = CourseAddView.as_view() 37 | 38 | 39 | def is_authenticated(request): 40 | if not request.user.is_authenticated: 41 | raise PermissionDenied 42 | 43 | 44 | def do_section(request, section_id): 45 | section = Section.objects.get(id=section_id) 46 | return render(request, 'courses/do_section.html', { 47 | 'section': section, 48 | }) 49 | 50 | 51 | def do_test(request, section_id): 52 | is_authenticated(request) 53 | section = Section.objects.get(id=section_id) 54 | if request.method == 'POST': 55 | data = {} 56 | for key, value in request.POST.items(): 57 | if key == 'csrfmiddlewaretoken': 58 | continue 59 | # {'question-1': '2'} 60 | question_id = key.split('-')[1] 61 | answer_id = request.POST.get(key) 62 | data[question_id] = answer_id 63 | perform_test(request.user, data, section) 64 | return redirect(reverse('show_results', args=(section.id,))) 65 | return render(request, 'courses/do_test.html', { 66 | 'section': section, 67 | }) 68 | 69 | 70 | def perform_test(user, data, section): 71 | with transaction.atomic(): 72 | UserAnswer.objects.filter(user=user, 73 | question__section=section).delete() 74 | for question_id, answer_id in data.items(): 75 | question = Question.objects.get(id=question_id) 76 | answer_id = int(answer_id) 77 | if answer_id not in question.answer_set.values_list('id', flat=True): 78 | raise SuspiciousOperation('Answer is not valid for this question') 79 | UserAnswer.objects.create(user=user, 80 | question=question, 81 | answer_id=answer_id, 82 | ) 83 | 84 | 85 | def calculate_score(user, section): 86 | questions = Question.objects.filter(section=section) 87 | correct_answers = UserAnswer.objects.filter( 88 | user=user, 89 | question__section=section, 90 | answer__correct=True 91 | ) 92 | return (correct_answers.count() / questions.count()) * 100 93 | 94 | 95 | def show_results(request, section_id): 96 | is_authenticated(request) 97 | section = Section.objects.get(id=section_id) 98 | return render(request, 'courses/show_results.html', { 99 | 'section': section, 100 | 'score': calculate_score(request.user, section) 101 | }) 102 | 103 | 104 | class SectionViewSet(viewsets.ModelViewSet): 105 | queryset = Section.objects.all() 106 | serializer_class = SectionSerializer 107 | 108 | @action(detail=True, methods=['get']) 109 | def questions(self, request, *args, **kwargs): 110 | section = self.get_object() 111 | data = [] 112 | for question in section.question_set.all(): 113 | question_data = {'id': question.id, 'answers': []} 114 | for answer in question.answer_set.all(): 115 | answer_data = {'id': answer.id, 'text': str(answer), } 116 | question_data['answers'].append(answer_data) 117 | data.append(question_data) 118 | return Response(data) 119 | 120 | @action(detail=True, methods=['put']) 121 | def test(self, request, *args, **kwargs): 122 | is_authenticated(request) 123 | section = self.get_object() 124 | perform_test(request.user, request.data, section) 125 | return Response() 126 | 127 | @action(detail=True, methods=['get']) 128 | def result(self, request, *args, **kwargs): 129 | is_authenticated(request) 130 | return Response({ 131 | 'score': calculate_score(request.user, self.get_object()) 132 | }) 133 | -------------------------------------------------------------------------------- /elearning/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Aga-Ma/e-learning-portal/b565ded17947b54e96c73d249d6f9ad7a936ec7d/elearning/__init__.py -------------------------------------------------------------------------------- /elearning/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for elearning project. 3 | """ 4 | 5 | import os 6 | 7 | import dj_database_url 8 | from decouple import config 9 | 10 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 11 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 12 | 13 | # Quick-start development settings - unsuitable for production 14 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 15 | 16 | # SECURITY WARNING: keep the secret key used in production secret! 17 | SECRET_KEY = 'svv*l+l2kub3h%4aty+d-fpmw)%s_%n#3aj$_d+w69d2#(8^=i' 18 | 19 | # SECURITY WARNING: don't run with debug turned on in production! 20 | DEBUG = config('DEBUG', default=False, cast=bool) 21 | 22 | ALLOWED_HOSTS = [ 23 | '127.0.0.1', 24 | 'elearningportal.herokuapp.com'] 25 | 26 | # Application definition 27 | 28 | INSTALLED_APPS = [ 29 | 'django.contrib.admin', 30 | 'django.contrib.auth', 31 | 'django.contrib.contenttypes', 32 | 'django.contrib.sessions', 33 | 'django.contrib.messages', 34 | 'django.contrib.staticfiles', 35 | 36 | 'rest_framework', 37 | 'widget_tweaks', 38 | 39 | 'courses', 40 | 'students', 41 | ] 42 | 43 | AUTH_USER_MODEL = 'students.User' 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | 54 | 'whitenoise.middleware.WhiteNoiseMiddleware', 55 | ] 56 | 57 | ROOT_URLCONF = 'elearning.urls' 58 | 59 | TEMPLATES = [ 60 | { 61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 62 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 63 | 'APP_DIRS': True, 64 | 'OPTIONS': { 65 | 'context_processors': [ 66 | 'django.template.context_processors.debug', 67 | 'django.template.context_processors.request', 68 | 'django.contrib.auth.context_processors.auth', 69 | 'django.contrib.messages.context_processors.messages', 70 | ], 71 | }, 72 | }, 73 | ] 74 | 75 | WSGI_APPLICATION = 'elearning.wsgi.application' 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.sqlite3', 83 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 84 | } 85 | } 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 102 | }, 103 | ] 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 120 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 121 | STATIC_URL = '/static/' 122 | 123 | STATICFILES_DIRS = [ 124 | os.path.join(BASE_DIR, 'static') 125 | ] 126 | 127 | STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' 128 | 129 | LOGIN_REDIRECT_URL = '/' 130 | 131 | REST_FRAMEWORK = { 132 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 133 | 'rest_framework.authentication.BasicAuthentication', 134 | 'rest_framework.authentication.SessionAuthentication', 135 | ) 136 | } 137 | 138 | # heroku settings 139 | prod_db = dj_database_url.config(conn_max_age=500) 140 | DATABASES['default'].update(prod_db) 141 | -------------------------------------------------------------------------------- /elearning/urls.py: -------------------------------------------------------------------------------- 1 | """elearning URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: re_path(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: re_path(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: re_path(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.urls import re_path, include 17 | from django.contrib import admin 18 | 19 | from rest_framework import routers 20 | 21 | from students.views import signup, student_detail 22 | from courses.views import course_detail, course_list, course_add, do_section, do_test, show_results, SectionViewSet 23 | from api.views import UserViewSet 24 | 25 | router = routers.DefaultRouter() 26 | router.register(r'users', UserViewSet) 27 | router.register(r'sections', SectionViewSet) 28 | 29 | 30 | urlpatterns = [ 31 | re_path(r'^$', course_list, name='home'), 32 | re_path(r'^admin/', admin.site.urls), 33 | re_path(r'^signup/$', signup, name='signup'), 34 | re_path('^', include(('django.contrib.auth.urls', 'user'), namespace='auth')), 35 | re_path(r'^course_detail/(?P\d+)/$', course_detail, name='course_detail'), 36 | re_path(r'^course_add/$', course_add, name='course_add'), 37 | re_path(r'^section/(?P\d+)/$', do_section, name='do_section'), 38 | re_path(r'^section/(?P\d+)/test/$', do_test, name='do_test'), 39 | re_path(r'^section/(?P\d+)/results/$', show_results, name='show_results'), 40 | re_path(r'^student_detail/$', student_detail, name='student_detail'), 41 | re_path(r'^api/', include(router.urls)), 42 | ] 43 | -------------------------------------------------------------------------------- /elearning/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for elearning project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "elearning.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "elearning.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.2.3 2 | certifi==2018.1.18 3 | chardet==3.0.4 4 | colorama==0.4.3 5 | dj-database-url==0.5.0 6 | Django==3.0.3 7 | djangorestframework==3.11.0 8 | django-widget-tweaks==1.4.5 9 | gunicorn==20.0.4 10 | httpie==1.0.3 11 | idna==2.6 12 | psycopg2==2.8.4 13 | Pygments==2.3.1 14 | python-decouple==3.3 15 | pytz==2017.3 16 | requests==2.21.0 17 | sqlparse==0.3.0 18 | urllib3==1.24.2 19 | whitenoise==5.0.1 -------------------------------------------------------------------------------- /static/css/app.css: -------------------------------------------------------------------------------- 1 | .navbar-brand { 2 | font-family: 'Peralta', cursive; 3 | } -------------------------------------------------------------------------------- /static/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v4.4.1 (https://getbootstrap.com/) 3 | * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t=t||self).bootstrap={},t.jQuery,t.Popper)}(this,function(t,g,u){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Y.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Se,popperConfig:null},Fe="show",Ue="out",We={HIDE:"hide"+Oe,HIDDEN:"hidden"+Oe,SHOW:"show"+Oe,SHOWN:"shown"+Oe,INSERTED:"inserted"+Oe,CLICK:"click"+Oe,FOCUSIN:"focusin"+Oe,FOCUSOUT:"focusout"+Oe,MOUSEENTER:"mouseenter"+Oe,MOUSELEAVE:"mouseleave"+Oe},qe="fade",Me="show",Ke=".tooltip-inner",Qe=".arrow",Be="hover",Ve="focus",Ye="click",ze="manual",Xe=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Me))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(qe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,this._getPopperConfig(a)),g(o).addClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===Ue&&e._leave(null,e)};if(g(this.tip).hasClass(qe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){function e(){n._hoverState!==Fe&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),g(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()}var n=this,i=this.getTipElement(),o=g.Event(this.constructor.Event.HIDE);if(g(this.element).trigger(o),!o.isDefaultPrevented()){if(g(i).removeClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ye]=!1,this._activeTrigger[Ve]=!1,this._activeTrigger[Be]=!1,g(this.tip).hasClass(qe)){var r=_.getTransitionDurationFromElement(i);g(i).one(_.TRANSITION_END,e).emulateTransitionEnd(r)}else e();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Pe+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ke)),this.getTitle()),g(t).removeClass(qe+" "+Me)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=we(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t=t||("function"==typeof this.config.title?this.config.title.call(this.element):this.config.title)},t._getPopperConfig=function(t){var e=this;return l({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Qe},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},{},this.config.popperConfig)},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,{},e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Re[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==ze){var e=t===Be?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Be?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),this._hideModalHandler=function(){i.element&&i.hide()},g(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");!this.element.getAttribute("title")&&"string"==t||(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Ve:Be]=!0),g(e.getTipElement()).hasClass(Me)||e._hoverState===Fe?e._hoverState=Fe:(clearTimeout(e._timeout),e._hoverState=Fe,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Fe&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Ve:Be]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=Ue,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===Ue&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==je.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,{},e,{},"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(Ae,t,this.constructor.DefaultType),t.sanitize&&(t.template=we(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Le);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(qe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ne),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ne,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.4.1"}},{key:"Default",get:function(){return xe}},{key:"NAME",get:function(){return Ae}},{key:"DATA_KEY",get:function(){return Ne}},{key:"Event",get:function(){return We}},{key:"EVENT_KEY",get:function(){return Oe}},{key:"DefaultType",get:function(){return He}}]),i}();g.fn[Ae]=Xe._jQueryInterface,g.fn[Ae].Constructor=Xe,g.fn[Ae].noConflict=function(){return g.fn[Ae]=ke,Xe._jQueryInterface};var $e="popover",Ge="bs.popover",Je="."+Ge,Ze=g.fn[$e],tn="bs-popover",en=new RegExp("(^|\\s)"+tn+"\\S+","g"),nn=l({},Xe.Default,{placement:"right",trigger:"click",content:"",template:''}),on=l({},Xe.DefaultType,{content:"(string|element|function)"}),rn="fade",sn="show",an=".popover-header",ln=".popover-body",cn={HIDE:"hide"+Je,HIDDEN:"hidden"+Je,SHOW:"show"+Je,SHOWN:"shown"+Je,INSERTED:"inserted"+Je,CLICK:"click"+Je,FOCUSIN:"focusin"+Je,FOCUSOUT:"focusout"+Je,MOUSEENTER:"mouseenter"+Je,MOUSELEAVE:"mouseleave"+Je},hn=function(t){function i(){return t.apply(this,arguments)||this}!function(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}(i,t);var e=i.prototype;return e.isWithContent=function(){return this.getTitle()||this._getContent()},e.addAttachmentClass=function(t){g(this.getTipElement()).addClass(tn+"-"+t)},e.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},e.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(an),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(ln),e),t.removeClass(rn+" "+sn)},e._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},e._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(en);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||t+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp($),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+$),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),ne=function(e,t,n){var r="0x"+t-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){(s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=k),o=(l=h(t)).length;while(o--)l[o]="#"+s+" "+xe(l[o]);c=l.join(","),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+k+"-]").length||v.push("~="),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+k+"+*").length||v.push(".#.+[+~]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",$)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&p(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):"string"!=typeof n?k.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i,ge={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;nx",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\.(.+)|)/;function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v){n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(R)||[""]).length;while(l--)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(R)||[""]).length;while(l--)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,"events")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t\x20\t\r\n\f]*)[^>]*)\/>/gi,qe=/\s*$/g;function Oe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&k(e).children("tbody")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function Re(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;n")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Vt,Gt=[],Yt=/(=)\?(?=&|$)|\?\?/;k.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Gt.pop()||k.expando+"_"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Yt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,"$1"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||k.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,"position"),c=k(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=k.css(e,"top"),u=k.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===k.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===k.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,"borderTopWidth",!0),i.left+=k.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-k.css(r,"marginTop",!0),left:t.left-i.left-k.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===k.css(e,"position"))e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),k.each(["top","left"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){if(t)return t=_e(e,n),$e.test(t)?k(e).position()[n]+"px":t})}),k.each({Height:"height",Width:"width"},function(a,s){k.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){k.fn[n]=function(e,t){return 0(window.devicePixelRatio||1)?"translate("+e+"px, "+u+"px)":"translate3d("+e+"px, "+u+"px, 0)",l)):Object.assign({},n,((t={})[v]=a?u+"px":"",t[m]=d?e+"px":"",t.transform="",t))}function j(e){return e.replace(/left|right|bottom|top/g,(function(e){return _[e]}))}function D(e){return e.replace(/start|end/g,(function(e){return U[e]}))}function E(e,t){var r=!(!t.getRootNode||!t.getRootNode().host);if(e.contains(t))return!0;if(r)do{if(t&&e.isSameNode(t))return!0;t=t.parentNode||t.host}while(t);return!1}function k(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function L(e,o){if("viewport"===o)e=k({width:(e=r(e)).innerWidth,height:e.innerHeight,x:0,y:0});else if(i(o))e=t(o);else{var a=s(e);e=r(a),o=n(a),(a=p(s(a),e)).height=Math.max(a.height,e.innerHeight),a.width=Math.max(a.width,e.innerWidth),a.x=-o.scrollLeft,a.y=-o.scrollTop,e=k(a)}return e}function P(e,t,n){return t="clippingParents"===t?function(e){var t=l(e),r=0<=["absolute","fixed"].indexOf(d(e).position)&&i(e)?h(e):e;return o(r)?t.filter((function(e){return o(e)&&E(e,r)})):[]}(e):[].concat(t),(n=(n=[].concat(t,[n])).reduce((function(t,n){var o=L(e,n),p=r(n=i(n)?n:s(e)),c=i(n)?d(n):{};parseFloat(c.borderTopWidth);var u=parseFloat(c.borderRightWidth)||0,l=parseFloat(c.borderBottomWidth)||0,m=parseFloat(c.borderLeftWidth)||0;c="html"===a(n);var h=f(n),v=n.clientWidth+u,g=n.clientHeight+l;return c&&50m?u:c?p.innerWidth-v-h:n.offsetWidth-v,p=c?p.innerHeight-g:n.offsetHeight-g,n=c?h:n.clientLeft,t.top=Math.max(o.top+l,t.top),t.right=Math.min(o.right-u,t.right),t.bottom=Math.min(o.bottom-p,t.bottom),t.left=Math.max(o.left+n,t.left),t}),L(e,n[0]))).width=n.right-n.left,n.height=n.bottom-n.top,n.x=n.left,n.y=n.top,n}function W(e){return Object.assign({},{top:0,right:0,bottom:0,left:0},{},e)}function B(e,t){return t.reduce((function(t,r){return t[r]=e,t}),{})}function H(e,r){void 0===r&&(r={});var n=r;r=void 0===(r=n.placement)?e.placement:r;var i=n.boundary,a=void 0===i?"clippingParents":i,f=void 0===(i=n.rootBoundary)?"viewport":i;i=void 0===(i=n.elementContext)?"popper":i;var p=n.altBoundary,c=void 0!==p&&p;n=W("number"!=typeof(n=void 0===(n=n.padding)?0:n)?n:B(n,q));var u=e.elements.reference;p=e.rects.popper,a=P(o(c=e.elements[c?"popper"===i?"reference":"popper":i])?c:s(e.elements.popper),a,f),c=O({reference:f=t(u),element:p,strategy:"absolute",placement:r}),p=k(Object.assign({},p,{},c)),f="popper"===i?p:f;var d={top:a.top-f.top+n.top,bottom:f.bottom-a.bottom+n.bottom,left:a.left-f.left+n.left,right:f.right-a.right+n.right};if(e=e.modifiersData.offset,"popper"===i&&e){var l=e[r];Object.keys(d).forEach((function(e){var t=0<=["right","bottom"].indexOf(e)?1:-1,r=0<=["top","bottom"].indexOf(e)?"y":"x";d[e]+=l[r]*t}))}return d}function R(e,t,r){return void 0===r&&(r={x:0,y:0}),{top:e.top-t.height-r.y,right:e.right-t.width+r.x,bottom:e.bottom-t.height+r.y,left:e.left-t.width-r.x}}function T(e){return["top","right","bottom","left"].some((function(t){return 0<=e[t]}))}var q=["top","bottom","right","left"],S=q.reduce((function(e,t){return e.concat([t+"-start",t+"-end"])}),[]),A=[].concat(q,["auto"]).reduce((function(e,t){return e.concat([t,t+"-start",t+"-end"])}),[]),C="beforeRead read afterRead beforeMain main afterMain beforeWrite write afterWrite".split(" "),F={placement:"bottom",modifiers:[],strategy:"absolute"},I={passive:!0},N={top:"auto",right:"auto",bottom:"auto",left:"auto"},_={left:"right",right:"left",bottom:"top",top:"bottom"},U={start:"end",end:"start"},V=[{name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(e){var t=e.state,n=e.instance,o=(e=e.options).scroll,i=void 0===o||o,a=void 0===(e=e.resize)||e,s=r(t.elements.popper),f=[].concat(t.scrollParents.reference,t.scrollParents.popper);return i&&f.forEach((function(e){e.addEventListener("scroll",n.update,I)})),a&&s.addEventListener("resize",n.update,I),function(){i&&f.forEach((function(e){e.removeEventListener("scroll",n.update,I)})),a&&s.removeEventListener("resize",n.update,I)}},data:{}},{name:"popperOffsets",enabled:!0,phase:"read",fn:function(e){var t=e.state;t.modifiersData[e.name]=O({reference:t.rects.reference,element:t.rects.popper,strategy:"absolute",placement:t.placement})},data:{}},{name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(e){var t=e.state,r=e.options;e=void 0===(e=r.gpuAcceleration)||e,r=void 0===(r=r.adaptive)||r,e={placement:b(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:e},t.styles.popper=Object.assign({},t.styles.popper,{},M(Object.assign({},e,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:r}))),null!=t.modifiersData.arrow&&(t.styles.arrow=Object.assign({},t.styles.arrow,{},M(Object.assign({},e,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})},data:{}},{name:"applyStyles",enabled:!0,phase:"write",fn:function(e){var t=e.state;Object.keys(t.elements).forEach((function(e){var r=t.styles[e]||{},n=t.attributes[e]||{},o=t.elements[e];i(o)&&a(o)&&(Object.assign(o.style,r),Object.keys(n).forEach((function(e){var t=n[e];!1===t?o.removeAttribute(e):o.setAttribute(e,!0===t?"":t)})))}))},effect:function(e){var t=e.state,r={position:"absolute",left:"0",top:"0",margin:"0"};return Object.assign(t.elements.popper.style,r),function(){Object.keys(t.elements).forEach((function(e){var n=t.elements[e],o=Object.keys(t.styles.hasOwnProperty(e)?Object.assign({},t.styles[e]):r);e=t.attributes[e]||{},o=o.reduce((function(e,t){var r;return Object.assign({},e,((r={})[String(t)]="",r))}),{}),i(n)&&a(n)&&(Object.assign(n.style,o),Object.keys(e).forEach((function(e){return n.removeAttribute(e)})))}))}},requires:["computeStyles"]},{name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(e){var t=e.state,r=e.name,n=void 0===(e=e.options.offset)?[0,0]:e,o=(e=A.reduce((function(e,r){var o=t.rects,i=b(r),a=0<=["left","top"].indexOf(i)?-1:1,s="function"==typeof n?n(Object.assign({},o,{placement:r})):n;return o=(o=s[0])||0,s=((s=s[1])||0)*a,i=0<=["left","right"].indexOf(i)?{x:s,y:o}:{x:o,y:s},e[r]=i,e}),{}))[t.placement],i=o.y;t.modifiersData.popperOffsets.x+=o.x,t.modifiersData.popperOffsets.y+=i,t.modifiersData[r]=e}},{name:"flip",enabled:!0,phase:"main",fn:function(e){var t=e.state,r=e.options;if(e=e.name,!t.modifiersData[e]._skip){var n=r.fallbackPlacements,o=r.padding,i=r.boundary,a=r.rootBoundary,s=void 0===(r=r.flipVariations)||r,f=b(r=t.options.placement);n=n||(f!==r&&s?function(e){if("auto"===b(e))return[];var t=j(e);return[D(e),t,D(t)]}(r):[j(r)]);var p=function(e,t){var r=new Set;return e.filter((function(e){if(e=t(e),!r.has(e))return r.add(e),!0}))}([r].concat(n).reduce((function(e,r){return"auto"===b(r)?e.concat(function(e,t){void 0===t&&(t={});var r=t.boundary,n=t.rootBoundary,o=t.padding,i=t.flipVariations,a=t.placement.split("-")[1],s=(a?i?S:S.filter((function(e){return e.split("-")[1]===a})):q).reduce((function(t,i){return t[i]=H(e,{placement:i,boundary:r,rootBoundary:n,padding:o})[b(i)],t}),{});return Object.keys(s).sort((function(e,t){return s[e]-s[t]}))}(t,{placement:r,boundary:i,rootBoundary:a,padding:o,flipVariations:s})):e.concat(r)}),[]),(function(e){return e}));n=t.rects.reference,r=t.rects.popper;var c=new Map;f=!0;for(var u=p[0],d=0;dr[g]&&(h=j(h)),g=j(h),(m=[0>=y[m],0>=y[h],0>=y[g]]).every((function(e){return e}))){u=l,f=!1;break}c.set(l,m)}if(f)for(n=function(e){var t=p.find((function(t){if(t=c.get(t))return t.slice(0,e).every((function(e){return e}))}));if(t)return u=t,"break"},r=s?3:1;0 5 |

Sign up

6 |
7 | {% csrf_token %} 8 | {% include 'includes/form.html' %} 9 | 10 |
11 | 12 | {% endblock %} -------------------------------------------------------------------------------- /students/templates/students/student_detail.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block breadcrumb %} 4 | 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
    9 | {% for course, entries in scores %} 10 |
  • {{ course }}
  • 11 |
      12 | {% for section, score in entries %} 13 |
    • {{ section }}: {{ score }}
    • 14 | {% endfor %} 15 |
    16 | {% endfor %} 17 |
18 | {% endblock %} -------------------------------------------------------------------------------- /students/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /students/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import login 2 | from django.core.exceptions import PermissionDenied 3 | from django.shortcuts import render, redirect 4 | 5 | from courses.models import Course 6 | from courses.views import calculate_score 7 | 8 | from .forms import CustomUserCreationForm 9 | 10 | 11 | def signup(request): 12 | if request.method == 'POST': 13 | form = CustomUserCreationForm(request.POST) 14 | if form.is_valid(): 15 | user = form.save() 16 | login(request, user) 17 | return redirect('home') 18 | else: 19 | form = CustomUserCreationForm() 20 | return render(request, 'students/signup.html', {'form': form}) 21 | 22 | 23 | def get_all_scores_for_user(user): 24 | scores = [] 25 | for course in Course.objects.all(): 26 | course_scores = [] 27 | for section in course.section_set.order_by('number'): 28 | course_scores.append((section, calculate_score(user, section),)) 29 | scores.append((course, course_scores),) 30 | return scores 31 | 32 | 33 | def student_detail(request): 34 | if not request.user.is_authenticated: 35 | raise PermissionDenied 36 | student = request.user 37 | return render(request, 'students/student_detail.html', { 38 | 'student': student, 39 | 'scores': get_all_scores_for_user(student), 40 | }) 41 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | Django E-learning 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% block body %} 15 | 36 | 37 |
38 | 42 | {% block content %} 43 | {% endblock %} 44 |
45 | {% endblock body %} 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /templates/includes/form.html: -------------------------------------------------------------------------------- 1 | {% load widget_tweaks %} 2 | 3 | {% for field in form %} 4 |
5 | {{ field.label_tag }} 6 | 7 | {% if form.is_bound %} 8 | {% if field.errors %} 9 | {% render_field field class="form-control is-invalid" %} 10 | {% for error in field.errors %} 11 |
12 | {{ error }} 13 |
14 | {% endfor %} 15 | {% else %} 16 | {% render_field field class="form-control is-valid" %} 17 | {% endif %} 18 | {% else %} 19 | {% render_field field class="form-control" %} 20 | {% endif %} 21 | 22 | {% if field.help_text %} 23 | 24 | {{ field.help_text|safe }} 25 | 26 | {% endif %} 27 |
28 | {% endfor %} -------------------------------------------------------------------------------- /templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 |
8 |

Logout

9 |

You have been logged out.

10 |
11 |
12 |
13 | 14 | {% endblock %} -------------------------------------------------------------------------------- /templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | 5 |
6 |
7 |
8 |

Login

9 |
10 | {% csrf_token %} 11 | {{ form.as_p }} 12 |
13 |
14 |
15 |
16 |
17 | 18 | {% endblock %} --------------------------------------------------------------------------------