├── chemtrack ├── __init__.py ├── models.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── chemtrack_app ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0013_auto_20201001_1145.py │ ├── 0002_category_template_order.py │ ├── 0012_auto_20201001_1145.py │ ├── 0010_auto_20201001_1134.py │ ├── 0007_auto_20200929_1158.py │ ├── 0011_auto_20201001_1143.py │ ├── 0003_auto_20200414_1614.py │ ├── 0008_auto_20200929_1430.py │ ├── 0006_auto_20200928_1129.py │ ├── 0009_auto_20201001_1132.py │ ├── 0005_auto_20200904_1721.py │ ├── 0004_auto_20200825_1241.py │ └── 0001_initial.py ├── templatetags │ ├── __init__.py │ ├── groups.py │ └── record.py ├── tests.py ├── apps.py ├── controllers │ ├── groups.py │ ├── __init__.py │ ├── templates.py │ └── record.py ├── views │ ├── index.py │ ├── __init__.py │ ├── check_template.py │ ├── student_records_view.py │ ├── teacher_records_view.py │ ├── student_dafts_view.py │ ├── new_draft.py │ ├── teacher_search_student.py │ ├── view_record.py │ └── ajax.py ├── templates │ ├── registration │ │ ├── logged_out.html │ │ └── login.html │ ├── chemtrack_app │ │ ├── students │ │ │ ├── new_draft.html │ │ │ ├── see_records.html │ │ │ └── see_drafts.html │ │ ├── teachers │ │ │ ├── see_records.html │ │ │ └── teacher_search_student.html │ │ └── record_management │ │ │ ├── admin.html │ │ │ └── record.html │ └── base.html ├── static │ ├── js │ │ ├── base.js │ │ └── jquery.js │ └── css │ │ └── base.css ├── admin.py ├── urls.py ├── views_old.py └── models.py ├── .idea ├── .gitignore ├── vcs.xml ├── inspectionProfiles │ └── profiles_settings.xml ├── modules.xml ├── chemtrack.iml └── misc.xml ├── db.sqlite3 ├── record_template └── template.json ├── Documentation.txt ├── manage.py └── LICENSE /chemtrack/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chemtrack/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chemtrack_app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chemtrack_app/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chemtrack_app/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imperialchem/skills-passport/master/db.sqlite3 -------------------------------------------------------------------------------- /chemtrack_app/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /chemtrack_app/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ChemtrackAppConfig(AppConfig): 5 | name = 'chemtrack_app' 6 | -------------------------------------------------------------------------------- /chemtrack_app/controllers/groups.py: -------------------------------------------------------------------------------- 1 | def is_student(user): 2 | return user.groups.filter(name='student').exists() 3 | 4 | def is_teacher(user): 5 | return user.groups.filter(name='teacher').exists() -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /chemtrack_app/views/index.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | 3 | def index(request): 4 | template = 'base.html' 5 | context = {} 6 | return render(request, template, context) 7 | -------------------------------------------------------------------------------- /chemtrack_app/templates/registration/logged_out.html: -------------------------------------------------------------------------------- 1 | {% extends "../base.html" %} 2 | 3 | {% block content %} 4 |

You just logged out

5 | Click here to login again. 6 | {% endblock %} -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /chemtrack_app/templatetags/groups.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import Group 2 | from django import template 3 | 4 | 5 | register = template.Library() 6 | 7 | @register.filter(name='has_group') 8 | def has_group(user, group_name): 9 | return user.groups.filter(name=group_name).exists() -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/chemtrack.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /record_template/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "categories": 3 | [{ 4 | "id": 1, 5 | "descriptors" : [1,2,3] 6 | }, 7 | { 8 | "id": 2, 9 | "descriptors" : [4,5,6] 10 | }, 11 | { 12 | "id": 3, 13 | "descriptors" : [7,8,9] 14 | } 15 | ] 16 | 17 | } -------------------------------------------------------------------------------- /Documentation.txt: -------------------------------------------------------------------------------- 1 | chemtrack app is the main application : 2 | 3 | The models define all of the data that is stored in the DB 4 | 5 | 6 | The current template used for new records is saved under "record_template/template.json" 7 | - Modify it when we want to change the template of newly created records 8 | - Whenever we modify it, go to /check_template/ to see if the template is valid or not -------------------------------------------------------------------------------- /chemtrack_app/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # For group checks 3 | from .groups import is_student 4 | from .groups import is_teacher 5 | 6 | # For record management 7 | from .record import create_record 8 | from .record import has_acces_to 9 | from .record import can_modify 10 | from .record import can_submit 11 | 12 | # For admin check template 13 | from .templates import check_template 14 | from .templates import get_template -------------------------------------------------------------------------------- /chemtrack_app/static/js/base.js: -------------------------------------------------------------------------------- 1 | /* Set the width of the side navigation to 250px */ 2 | function openNav() { 3 | document.getElementById("mySidenav").style.width = "250px"; 4 | document.getElementById("overlay").style.display = "block"; 5 | } 6 | 7 | /* Set the width of the side navigation to 0 */ 8 | function closeNav() { 9 | document.getElementById("mySidenav").style.width = "0"; 10 | document.getElementById("overlay").style.display = "None"; 11 | } -------------------------------------------------------------------------------- /chemtrack_app/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Assignment, Category_template, Descriptor_template, Record, Record_category, Record_descriptor, Profile 4 | 5 | admin.site.register(Assignment) 6 | admin.site.register(Category_template) 7 | admin.site.register(Descriptor_template) 8 | admin.site.register(Record) 9 | admin.site.register(Record_category) 10 | admin.site.register(Record_descriptor) 11 | admin.site.register(Profile) -------------------------------------------------------------------------------- /chemtrack/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for chemtrack project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chemtrack.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /chemtrack/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for chemtrack 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/3.0/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', 'chemtrack.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /chemtrack_app/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .index import index 2 | from .new_draft import create_draft 3 | from .student_dafts_view import student_dafts_view 4 | from .student_records_view import student_records_view 5 | from .teacher_records_view import teacher_records_view 6 | from .teacher_search_student import teacher_search_student 7 | from .view_record import view_record 8 | from .ajax import update_level 9 | from .ajax import update_statement 10 | 11 | from .check_template import check_template -------------------------------------------------------------------------------- /chemtrack_app/migrations/0013_auto_20201001_1145.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-10-01 10:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0012_auto_20201001_1145'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='record', 15 | name='levels', 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /chemtrack_app/migrations/0002_category_template_order.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-14 09:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='category_template', 15 | name='order', 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /chemtrack_app/views/check_template.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.http import HttpResponse 3 | from django.shortcuts import render, redirect 4 | from .. import controllers 5 | 6 | 7 | login_required() 8 | def check_template(request): 9 | 10 | template_ok, err = controllers.check_template() 11 | if template_ok: 12 | return HttpResponse("Template Valid !") 13 | else: 14 | return HttpResponse("Template not valid.
Check :
"+err) -------------------------------------------------------------------------------- /chemtrack_app/migrations/0012_auto_20201001_1145.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-10-01 10:45 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0011_auto_20201001_1143'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='record', 15 | name='levels', 16 | field=models.IntegerField(default=0, max_length=200), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /chemtrack_app/templates/chemtrack_app/students/new_draft.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base.html" %} 2 | 3 | {% block content %} 4 | 5 |

New Draft

6 | 7 |
8 | 9 | {% csrf_token %} 10 | Name:
11 | Date:

12 | Description:
13 |

14 | 15 |
16 | 17 | {% endblock %} -------------------------------------------------------------------------------- /chemtrack_app/views/student_records_view.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.shortcuts import render, redirect 3 | from .. import controllers 4 | from ..models import Record 5 | 6 | login_required() 7 | @user_passes_test(controllers.is_student) 8 | def student_records_view(request): 9 | 10 | records = Record.objects.filter(student=request.user, state=4).order_by('-pk') 11 | 12 | 13 | template = 'chemtrack_app/students/see_records.html' 14 | context = {"records":records} 15 | return render(request, template, context) -------------------------------------------------------------------------------- /chemtrack_app/migrations/0010_auto_20201001_1134.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-10-01 10:34 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0009_auto_20201001_1132'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='assignment', 15 | old_name='student_cid', 16 | new_name='student', 17 | ), 18 | migrations.RenameField( 19 | model_name='assignment', 20 | old_name='teacher_cid', 21 | new_name='teacher', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chemtrack.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /chemtrack_app/views/teacher_records_view.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.shortcuts import render, redirect 3 | from .. import controllers 4 | from ..models import Record, Assignment 5 | 6 | login_required() 7 | @user_passes_test(controllers.is_teacher) 8 | def teacher_records_view(request): 9 | 10 | student_cids = Assignment.objects.filter(teacher=request.user).values_list('student', flat=True) 11 | 12 | feedback = Record.objects.filter(student__in=student_cids, state=1) 13 | confirmation = Record.objects.filter(student__in=student_cids, state=3) 14 | 15 | 16 | template = 'chemtrack_app/teachers/see_records.html' 17 | context = {"feedback":feedback, "confirmation":confirmation} 18 | return render(request, template, context) -------------------------------------------------------------------------------- /chemtrack_app/views/student_dafts_view.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.shortcuts import render, redirect 3 | from .. import controllers 4 | from ..models import Record 5 | 6 | login_required() 7 | @user_passes_test(controllers.is_student) 8 | def student_dafts_view(request): 9 | 10 | drafts = Record.objects.filter(student=request.user, state=0) 11 | final_statements = Record.objects.filter(student=request.user, state=2) 12 | feedbacks = Record.objects.filter(student=request.user, state=1) 13 | confirmations = Record.objects.filter(student=request.user, state=3) 14 | 15 | 16 | template = 'chemtrack_app/students/see_drafts.html' 17 | context = {"drafts":drafts, "final_statements":final_statements, "feedbacks":feedbacks, "confirmations":confirmations} 18 | return render(request, template, context) -------------------------------------------------------------------------------- /chemtrack_app/migrations/0007_auto_20200929_1158.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-09-29 10:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0006_auto_20200928_1129'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='record_category', 15 | old_name='level', 16 | new_name='draft_level', 17 | ), 18 | migrations.AddField( 19 | model_name='record_category', 20 | name='feedback_level', 21 | field=models.IntegerField(default=0), 22 | ), 23 | migrations.AddField( 24 | model_name='record_category', 25 | name='final_level', 26 | field=models.IntegerField(default=0), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /chemtrack_app/templates/chemtrack_app/students/see_records.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base.html" %} 2 | 3 | {% block content %} 4 | 5 |

Records

6 | 7 | {% for record in records %} 8 | 9 |
10 |
11 |
Created the: {{record.date_creation}}
12 |
13 |
Title: {{record.name}}
14 |
Date: {{record.date}}
15 |
{{record.description|truncatewords:15}}
16 |
{{record.level }}
17 |
18 |
19 | {% endfor %} 20 | 21 | {% endblock %} -------------------------------------------------------------------------------- /chemtrack_app/migrations/0011_auto_20201001_1143.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-10-01 10:43 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('chemtrack_app', '0010_auto_20201001_1134'), 13 | ] 14 | 15 | operations = [ 16 | migrations.RemoveField( 17 | model_name='record', 18 | name='student_cid', 19 | ), 20 | migrations.AddField( 21 | model_name='record', 22 | name='student', 23 | field=models.ForeignKey(default=3, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 24 | preserve_default=False, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /chemtrack_app/migrations/0003_auto_20200414_1614.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-14 15:14 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0002_category_template_order'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='category_template', 15 | name='order', 16 | ), 17 | migrations.RemoveField( 18 | model_name='descriptor_template', 19 | name='category_id', 20 | ), 21 | migrations.RemoveField( 22 | model_name='descriptor_template', 23 | name='order', 24 | ), 25 | migrations.AddField( 26 | model_name='record_category', 27 | name='order', 28 | field=models.IntegerField(default=0), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /chemtrack_app/migrations/0008_auto_20200929_1430.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-09-29 13:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0007_auto_20200929_1158'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='record_descriptor', 15 | name='draft_statement', 16 | field=models.TextField(blank=True, default=''), 17 | ), 18 | migrations.AlterField( 19 | model_name='record_descriptor', 20 | name='feedback_statement', 21 | field=models.TextField(blank=True, default=''), 22 | ), 23 | migrations.AlterField( 24 | model_name='record_descriptor', 25 | name='final_statement', 26 | field=models.TextField(blank=True, default=''), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /chemtrack/urls.py: -------------------------------------------------------------------------------- 1 | """chemtrack URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.0/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: path('', 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: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path('accounts/', include('django.contrib.auth.urls')), 21 | path('admin/', admin.site.urls, name="admin"), 22 | path('', include('chemtrack_app.urls'), name="main"), 23 | ] 24 | -------------------------------------------------------------------------------- /chemtrack_app/migrations/0006_auto_20200928_1129.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-09-28 10:29 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0005_auto_20200904_1721'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='record_category', 15 | old_name='record_id', 16 | new_name='record', 17 | ), 18 | migrations.RenameField( 19 | model_name='record_category', 20 | old_name='template_id', 21 | new_name='template', 22 | ), 23 | migrations.RenameField( 24 | model_name='record_descriptor', 25 | old_name='category_id', 26 | new_name='category', 27 | ), 28 | migrations.RenameField( 29 | model_name='record_descriptor', 30 | old_name='template_id', 31 | new_name='template', 32 | ), 33 | ] 34 | -------------------------------------------------------------------------------- /chemtrack_app/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | 6 | path('', views.index, name='index'), 7 | 8 | path('check_template', views.check_template, name='template_check'), 9 | 10 | path('student_drafts/', views.student_dafts_view, name='student_drafts'), 11 | 12 | path('student_records/', views.student_records_view, name='student_records'), 13 | 14 | path('teacher_records/', views.teacher_records_view, name='teacher_records'), 15 | 16 | path('search_student/', views.teacher_search_student, name='search_student'), 17 | # Initial page to create a draft 18 | path('new_draft/', views.create_draft, name='new_draft'), 19 | # Main page, modifying drafts 20 | path('view_record/', views.view_record, name='view_record'), 21 | 22 | # Backend to modify level in DB 23 | path('ajax/update_level', views.update_level, name='update_level'), 24 | 25 | path('ajax/update_statement', views.update_statement, name='update_statement') 26 | ] -------------------------------------------------------------------------------- /chemtrack_app/migrations/0009_auto_20201001_1132.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-10-01 10:32 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('chemtrack_app', '0008_auto_20200929_1430'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='assignment', 18 | name='student_cid', 19 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignment_as_student', to=settings.AUTH_USER_MODEL), 20 | ), 21 | migrations.AlterField( 22 | model_name='assignment', 23 | name='teacher_cid', 24 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='assignment_as_teacher', to=settings.AUTH_USER_MODEL), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /chemtrack_app/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | {% extends "../base.html" %} 2 | 3 | {% block content %} 4 | 5 |

Login

6 | 7 | {% if form.errors %} 8 |

Your username and password didn't match. Please try again.

9 | {% endif %} 10 | 11 | {% if next %} 12 | {% if user.is_authenticated %} 13 |

Your account doesn't have access to this page. To proceed, 14 | please login with an account that has access.

15 | {% else %} 16 |

Please login to see this page.

17 | {% endif %} 18 | {% endif %} 19 | 20 |
21 | {% csrf_token %} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
{{ form.username.label_tag }}{{ form.username }}
{{ form.password.label_tag }}{{ form.password }}
32 | 33 | 34 |
35 | 36 | {% endblock %} -------------------------------------------------------------------------------- /chemtrack_app/migrations/0005_auto_20200904_1721.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-09-04 16:21 2 | 3 | import datetime 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('chemtrack_app', '0004_auto_20200825_1241'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='record', 19 | name='date', 20 | field=models.DateField(default=datetime.date.today), 21 | ), 22 | migrations.CreateModel( 23 | name='Profile', 24 | fields=[ 25 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 26 | ('cid', models.TextField(blank=True, default='', max_length=15)), 27 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 28 | ], 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /chemtrack_app/views/new_draft.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.shortcuts import render, redirect 3 | from .. import controllers 4 | from django import forms 5 | 6 | 7 | class NewDarftForm(forms.Form): 8 | name = forms.CharField(required=True) 9 | description = forms.CharField(required = False) 10 | date = forms.DateField(required=True) 11 | 12 | 13 | login_required() 14 | @user_passes_test(controllers.is_student) 15 | def create_draft(request): 16 | 17 | if request.method == 'GET': 18 | template = 'chemtrack_app/students/new_draft.html' 19 | context = {} 20 | return render(request, template, context) 21 | elif request.method == 'POST': 22 | form = NewDarftForm(request.POST) 23 | 24 | if form.is_valid(): 25 | new_id = controllers.create_record(request.user, form.cleaned_data["name"], form.cleaned_data["description"], form.cleaned_data["date"]) 26 | return redirect('view_record', record_id=new_id) 27 | 28 | else: 29 | return redirect('new_draft') 30 | else: 31 | return redirect('/') 32 | -------------------------------------------------------------------------------- /chemtrack_app/views_old.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from django.http import HttpResponse 3 | from django.contrib.auth.decorators import login_required, user_passes_test 4 | from . import models 5 | from django.template import loader 6 | 7 | from .controllers import check_template as controller_check_template 8 | 9 | 10 | 11 | def index(request): 12 | template = 'base.html' 13 | context = {} 14 | return render(request, template, context) 15 | 16 | 17 | login_required() 18 | def detail(request, question_id): 19 | return HttpResponse("You're looking at question %s." % question_id) 20 | 21 | def results(request, question_id): 22 | response = "You're looking at the results of question %s." 23 | return HttpResponse(response % question_id) 24 | 25 | def vote(request, question_id): 26 | return HttpResponse("You're voting on question %s." % question_id) 27 | 28 | login_required() 29 | def check_template(request): 30 | 31 | template_ok, err = controller_check_template.check_template() 32 | if template_ok: 33 | return HttpResponse("Template Valid !") 34 | else: 35 | return HttpResponse("Template not valid.
Check :
"+err) 36 | 37 | -------------------------------------------------------------------------------- /chemtrack_app/views/teacher_search_student.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.shortcuts import render, redirect 3 | from .. import controllers 4 | from ..models import Record, Profile 5 | 6 | login_required() 7 | @user_passes_test(controllers.is_teacher) 8 | def teacher_search_student(request): 9 | drafts = [] 10 | final_statements = [] 11 | feedbacks = [] 12 | confirmations = [] 13 | records = [] 14 | search = False 15 | found = False 16 | cid = "" 17 | student = None 18 | 19 | if "student_cid" in request.GET: 20 | student = Profile.objects.filter(cid=request.GET["student_cid"]) 21 | search = True 22 | cid = request.GET["student_cid"] 23 | if len(student) == 1: 24 | student = student[0].user 25 | 26 | drafts = Record.objects.filter(student=student, state=0) 27 | final_statements = Record.objects.filter(student=student, state=2) 28 | feedbacks = Record.objects.filter(student=student, state=1) 29 | confirmations = Record.objects.filter(student=student, state=3) 30 | records = Record.objects.filter(student=student, state=4) 31 | found = True 32 | 33 | template = 'chemtrack_app/teachers/teacher_search_student.html' 34 | context = {"student":student, "records":records, "search":search, "found":found, "drafts":drafts, "final_statements":final_statements, "feedbacks":feedbacks, "confirmations":confirmations} 35 | return render(request, template, context) -------------------------------------------------------------------------------- /chemtrack_app/templates/chemtrack_app/teachers/see_records.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base.html" %} 2 | 3 | {% block content %} 4 | 5 |

Waiting for your feedback

6 | 7 | {% for record in feedback %} 8 | 9 |
10 |
11 |
Created the: {{record.date_creation}}
12 |
13 |
Title: {{record.name}}
14 |
Date: {{record.date}}
15 |
{{record.description|truncatewords:15}}
16 |
{{record.level }}
17 |
18 |
19 | {% endfor %} 20 | 21 |

Waiting for your confirmation

22 | 23 | {% for record in confirmations %} 24 | 25 |
26 |
27 |
Created the: {{record.date_creation}}
28 |
29 |
Title: {{record.name}}
30 |
Date: {{record.date}}
31 |
{{record.description|truncatewords:15}}
32 |
{{record.level }}
33 |
34 |
35 | {% endfor %} 36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /chemtrack_app/templatetags/record.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from .. import controllers 3 | 4 | 5 | register = template.Library() 6 | 7 | @register.simple_tag(name='is_descriptor_selected') 8 | def is_descriptor_selected(descriptor, number, button_level): 9 | if number == 0 and descriptor.draft_level == button_level: 10 | return "selected" 11 | elif number == 1 and descriptor.feedback_level == button_level: 12 | return "selected" 13 | elif number == 2 and descriptor.final_level == button_level: 14 | return "selected" 15 | else: 16 | return "" 17 | 18 | 19 | @register.simple_tag(name='can_modify') 20 | def can_modify(record, user, state): 21 | return controllers.can_modify(record, user, state) 22 | 23 | @register.simple_tag(name='list_levels_generate') 24 | def list_levels_generate(state): 25 | if state == 0 or state == 2: 26 | return "123456" 27 | else: 28 | return "-=+" 29 | 30 | @register.simple_tag(name='pre_select') 31 | def pre_select(record, state): 32 | """ 33 | Function that says if the descriptor state should be displayed by default when the page is loaded 34 | state is either 0,1,2: 35 | 0: draft_level & draft_statement 36 | 37 | """ 38 | display = "" 39 | if record.state == 0: 40 | if state == 0: 41 | display = "selected" 42 | elif record.state == 1: 43 | if state == 0 or state == 1: 44 | display = "selected" 45 | elif record.state == 2: 46 | if state == 1 or state == 2: 47 | display = "selected" 48 | else: 49 | if state == 2: 50 | display = "selected" 51 | return display 52 | 53 | 54 | @register.simple_tag(name='can_submit') 55 | def can_submit(record, user): 56 | return controllers.can_submit(record, user) -------------------------------------------------------------------------------- /chemtrack_app/controllers/templates.py: -------------------------------------------------------------------------------- 1 | # 2 | # Function that checks whether the json template provided is valid with what 3 | # is in the database 4 | # Checks that the categories exist 5 | # Checks that the descriptors exist 6 | # 7 | # 8 | import json 9 | from django.conf import settings 10 | from ..models import Category_template, Descriptor_template 11 | 12 | 13 | def check_template(): 14 | result = True 15 | err = "" 16 | with open(settings.TEMPLATE_RECORD, "r") as file: 17 | 18 | data = json.load(file) 19 | 20 | for category in data["categories"]: 21 | 22 | # if the category is not in the database 23 | if not Category_template.objects.filter(pk=category["id"]).exists(): 24 | result = False 25 | err += f"Invalid category id {category['id']}
" 26 | 27 | # If a descriptor for this category is missing 28 | if not (Descriptor_template.objects.filter(pk__in=category["descriptors"]).count() == len(category["descriptors"])): 29 | result = False 30 | err += f"Invalid descriptor id in {category['descriptors']}
" 31 | 32 | 33 | return result, err 34 | 35 | def get_template(): 36 | with open(settings.TEMPLATE_RECORD, "r") as file: 37 | 38 | data = json.load(file) 39 | result = [] 40 | 41 | for category_id in data["categories"]: 42 | 43 | # if the category is not in the database 44 | category = Category_template.objects.filter(pk=category_id["id"])[0] 45 | 46 | descriptors = [] 47 | for desc_id in category_id["descriptors"]: 48 | desc = Descriptor_template.objects.filter(pk=desc_id)[0] 49 | descriptors.append(desc) 50 | 51 | result.append({"category":category, "descriptors":descriptors}) 52 | 53 | 54 | return result 55 | -------------------------------------------------------------------------------- /chemtrack_app/migrations/0004_auto_20200825_1241.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-08-25 11:41 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('chemtrack_app', '0003_auto_20200414_1614'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='record_category', 15 | name='order', 16 | ), 17 | migrations.RemoveField( 18 | model_name='record_descriptor', 19 | name='order', 20 | ), 21 | migrations.RemoveField( 22 | model_name='record_descriptor', 23 | name='record_id', 24 | ), 25 | migrations.AddField( 26 | model_name='record', 27 | name='description', 28 | field=models.TextField(default=''), 29 | ), 30 | migrations.AddField( 31 | model_name='record', 32 | name='name', 33 | field=models.CharField(default='', max_length=100), 34 | ), 35 | migrations.AlterField( 36 | model_name='category_template', 37 | name='description', 38 | field=models.TextField(default=''), 39 | ), 40 | migrations.AlterField( 41 | model_name='category_template', 42 | name='name', 43 | field=models.CharField(default='', max_length=200), 44 | ), 45 | migrations.AlterField( 46 | model_name='descriptor_template', 47 | name='description', 48 | field=models.TextField(default=''), 49 | ), 50 | migrations.AlterField( 51 | model_name='descriptor_template', 52 | name='name', 53 | field=models.CharField(default='', max_length=200), 54 | ), 55 | migrations.AlterField( 56 | model_name='record_descriptor', 57 | name='draft_statement', 58 | field=models.TextField(default=''), 59 | ), 60 | migrations.AlterField( 61 | model_name='record_descriptor', 62 | name='feedback_statement', 63 | field=models.TextField(default=''), 64 | ), 65 | migrations.AlterField( 66 | model_name='record_descriptor', 67 | name='final_statement', 68 | field=models.TextField(default=''), 69 | ), 70 | ] 71 | -------------------------------------------------------------------------------- /chemtrack_app/views/view_record.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.shortcuts import render, redirect 3 | from ..models import Record 4 | from .. import controllers 5 | from django import forms 6 | 7 | 8 | login_required() 9 | def view_record(request, record_id=-1): 10 | # If no id are in the url 11 | record= Record.objects.filter(pk=record_id) 12 | 13 | if len(record) != 1 or not controllers.has_acces_to(record[0], request.user): 14 | if controllers.is_student(request.user): 15 | return redirect('student_drafts') 16 | else: 17 | return redirect('/') 18 | else: 19 | # If the record does exist 20 | record = record[0] 21 | 22 | if "submit_record" in request.POST: 23 | if (record.state == 0 or record.state == 2) and controllers.is_student(request.user): 24 | record.state += 1 25 | record.save() 26 | elif (record.state == 1 or record.state == 3) and controllers.is_teacher(request.user): 27 | record.state += 1 28 | record.save() 29 | 30 | return redirect("view_record", record_id=record_id) 31 | 32 | elif "delete_record" in request.POST: 33 | if record.state == 0 and controllers.is_student(request.user): 34 | record.delete() 35 | return redirect("student_drafts") 36 | else: 37 | return redirect("view_record", record_id = record.pk) 38 | 39 | elif "modify_state" in request.POST and "new_state" in request.POST and controllers.is_teacher(request.user): 40 | new_state = int(request.POST["new_state"]) 41 | if new_state >= 0 and new_state <= 4: 42 | record.state = new_state 43 | record.save() 44 | 45 | 46 | return redirect("view_record", record_id=record_id) 47 | else: 48 | if "page" in request.GET and request.GET["page"] == "admin": 49 | template = 'chemtrack_app/record_management/admin.html' 50 | 51 | context = {"record":record} 52 | return render(request, template, context) 53 | 54 | 55 | else: 56 | template = 'chemtrack_app/record_management/record.html' 57 | 58 | 59 | context = {"record":record} 60 | return render(request, template, context) -------------------------------------------------------------------------------- /chemtrack_app/controllers/record.py: -------------------------------------------------------------------------------- 1 | from ..models import Record, Record_category, Record_descriptor, Descriptor_template, Category_template 2 | from django.utils import timezone 3 | import json 4 | from django.conf import settings 5 | from .templates import get_template 6 | from .groups import is_teacher, is_student 7 | 8 | # Helper function that creates new records based on the current template 9 | # 10 | # 11 | 12 | def create_record(student, name, description, date): 13 | """ 14 | 15 | :param student_cid: string : CID of the student 16 | :return: id of new record 17 | """ 18 | new_record = Record(date_creation=timezone.now(), student=student, name=name, description=description, date=date) 19 | new_record.save() 20 | 21 | 22 | template = get_template() 23 | 24 | 25 | for category in template: 26 | new_category = Record_category(template=category["category"], record=new_record) 27 | new_category.save() 28 | 29 | for descriptor in category["descriptors"]: 30 | 31 | new_descriptor= Record_descriptor(template=descriptor, category=new_category) 32 | new_descriptor.save() 33 | 34 | 35 | 36 | 37 | return new_record.pk 38 | 39 | def has_acces_to(record, user): 40 | if is_teacher(user): 41 | return True 42 | elif is_student(user) and record.student == user: 43 | return True 44 | else: 45 | return False 46 | 47 | def can_modify(record, user, state): 48 | """ 49 | Function that decides if the user can modify a specific field 50 | Teacher can modify only feedback 51 | Students can modify Drafts and Final statements 52 | 53 | state is the state of the field of the descriptor considered: 54 | 0:Draft 55 | 1:FeedBack 56 | 2:Final 57 | """ 58 | if has_acces_to(record, user): 59 | if is_teacher(user) and state == 1 and record.state == 1: 60 | return True 61 | elif is_student(user) and state == 0 and record.state == 0: 62 | return True 63 | elif is_student(user) and state == 2 and record.state == 2: 64 | return True 65 | return False 66 | 67 | def can_submit(record, user): 68 | result = False 69 | if record.state == 0 or record.state == 2: 70 | if is_student(user): 71 | result = True 72 | elif record.state == 1 or record.state == 3: 73 | if is_teacher(user): 74 | result = True 75 | return result -------------------------------------------------------------------------------- /chemtrack_app/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django.db.models.signals import post_save 4 | from django.dispatch import receiver 5 | import datetime 6 | 7 | class Assignment(models.Model): 8 | student = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assignment_as_student') 9 | teacher = models.ForeignKey(User, on_delete=models.CASCADE, related_name='assignment_as_teacher') 10 | 11 | def __str__(self): 12 | return f"Student: {self.student}, Teacher: {self.teacher}" 13 | 14 | class Profile(models.Model): 15 | user = models.OneToOneField(User, on_delete=models.CASCADE) 16 | cid = models.TextField(max_length=15, blank=True, default="") 17 | 18 | @receiver(post_save, sender=User) 19 | def create_user_profile(sender, instance, created, **kwargs): 20 | if created: 21 | Profile.objects.create(user=instance) 22 | 23 | @receiver(post_save, sender=User) 24 | def save_user_profile(sender, instance, **kwargs): 25 | instance.profile.save() 26 | 27 | class Category_template(models.Model): 28 | name = models.CharField(max_length=200, default="") 29 | description = models.TextField(default="") 30 | 31 | def __str__(self): 32 | return f"{self.pk} : {self.name}" 33 | 34 | 35 | class Descriptor_template(models.Model): 36 | name = models.CharField(max_length=200, default="") 37 | description = models.TextField(default="") 38 | 39 | def __str__(self): 40 | return f"{self.pk} : {self.name}" 41 | 42 | 43 | class Record(models.Model): 44 | date_creation = models.DateTimeField(auto_now_add=True) 45 | student = models.ForeignKey(User, on_delete=models.CASCADE) 46 | date = models.DateField(default=datetime.date.today) 47 | state = models.IntegerField(default=0) 48 | levels = models.IntegerField(default=0) 49 | name = models.CharField(max_length=100, default="") 50 | description = models.TextField(default="") 51 | 52 | 53 | class Record_category(models.Model): 54 | template = models.ForeignKey(Category_template, on_delete=models.CASCADE) 55 | record = models.ForeignKey(Record, on_delete=models.CASCADE) 56 | draft_level = models.IntegerField(default=0) 57 | feedback_level = models.IntegerField(default=0) 58 | final_level = models.IntegerField(default=0) 59 | 60 | 61 | class Record_descriptor(models.Model): 62 | template = models.ForeignKey(Descriptor_template, on_delete=models.CASCADE) 63 | category = models.ForeignKey(Record_category, on_delete=models.CASCADE) 64 | draft_level = models.IntegerField(default=0) 65 | feedback_level = models.IntegerField(default=0) 66 | final_level = models.IntegerField(default=0) 67 | draft_statement = models.TextField(default="", blank=True) 68 | feedback_statement = models.TextField(default="", blank=True) 69 | final_statement = models.TextField(default="", blank=True) -------------------------------------------------------------------------------- /chemtrack_app/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | {% load groups %} 3 | 4 | 5 | {% block title %}Chemtrack{% endblock %} 6 | 7 | 8 | 9 | {% block styles %}{% endblock %} 10 | 11 | 12 |
13 | 16 |
17 | {% if user.is_authenticated %} 18 | {{user.username}} - Log Out 19 | {% else %} 20 | Log In 21 | {% endif %} 22 |
23 |

Chemtrack

24 |
25 | 26 |
27 | × 28 | Home 29 | {% if user.is_authenticated %} 30 | {% if request.user|has_group:"student" %} 31 | Drafts 32 | Records 33 | {% elif request.user|has_group:"teacher"%} 34 | Waiting Records 35 | Search Student 36 | {% endif %} 37 | {% endif %} 38 | {% if request.user.is_superuser %} 39 | Check_template 40 | {% endif %} 41 |
42 | 43 | {% comment%} 44 | Overlay to help close the sidebar 45 | {% endcomment%} 46 |
47 | 48 |
49 | {% block content %} 50 | 51 |

52 | {% if user.is_authenticated %} 53 | 54 | Logged in as {{user.username}}, 55 | {% if request.user|has_group:"student" %} 56 | you are a student. 57 | {% elif request.user|has_group:"teacher"%} 58 | you are a teacher. 59 | {% endif %} 60 |
61 | Your CID is {{user.profile.cid}} 62 | {% else %} 63 | Chemtrack website is a website for the chemistry department.
64 | Please log in here 65 | {% endif %} 66 |

67 | 68 | {% endblock %} 69 |
70 | 71 | -------------------------------------------------------------------------------- /chemtrack_app/templates/chemtrack_app/students/see_drafts.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base.html" %} 2 | 3 | {% block content %} 4 | 5 |
New Draft
6 | 7 |

Final statement

8 | 9 | {% for record in final_statements %} 10 | 11 |
12 |
13 |
Created the: {{record.date_creation}}
14 |
15 |
Title: {{record.name}}
16 |
Date: {{record.date}}
17 |
{{record.description|truncatewords:15}}
18 |
{{record.level }}
19 |
20 |
21 | {% endfor %} 22 | 23 |

Drafts

24 | 25 | {% for record in drafts %} 26 | 27 |
28 |
29 |
Created the: {{record.date_creation}}
30 |
31 |
Title: {{record.name}}
32 |
Date: {{record.date}}
33 |
{{record.description|truncatewords:15}}
34 |
{{record.level }}
35 |
36 |
37 | {% endfor %} 38 | 39 |

Waiting for final confirmation

40 | 41 | {% for record in confirmations %} 42 | 43 |
44 |
45 |
Created the: {{record.date_creation}}
46 |
47 |
Title: {{record.name}}
48 |
Date: {{record.date}}
49 |
{{record.description|truncatewords:15}}
50 |
{{record.level }}
51 |
52 |
53 | {% endfor %} 54 | 55 |

Waiting for feedback

56 | 57 | {% for record in feedbacks %} 58 | 59 |
60 |
61 |
Created the: {{record.date_creation}}
62 |
63 |
Title: {{record.name}}
64 |
Date: {{record.date}}
65 |
{{record.description|truncatewords:15}}
66 |
{{record.level }}
67 |
68 |
69 | {% endfor %} 70 | {% endblock %} -------------------------------------------------------------------------------- /chemtrack_app/views/ajax.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required, user_passes_test 2 | from django.shortcuts import render, redirect 3 | from .. import controllers 4 | from ..models import Record_descriptor 5 | from django import forms 6 | from django.http import JsonResponse 7 | 8 | 9 | class update_level_form(forms.Form): 10 | new_level = forms.IntegerField(required=True) 11 | descriptor = forms.CharField(required = True) 12 | state = forms.IntegerField(required=True) 13 | 14 | 15 | class update_statement_form(forms.Form): 16 | new_text = forms.CharField() 17 | descriptor = forms.IntegerField(required = True) 18 | state = forms.IntegerField(required=True) 19 | 20 | def check_level_values(new_level, state): 21 | if state == 0 or state == 2: 22 | if new_level >= 1 or new_level <= 6: 23 | return True 24 | elif state == 1: 25 | if new_level >= 1 or new_level <= 3: 26 | return True 27 | return False 28 | 29 | 30 | def check_text_values(new_text, state): 31 | """ 32 | Overkill function 33 | There in case in the future checks want to be made on the content of the statements 34 | """ 35 | if state == 0 or state == 2: 36 | return True 37 | elif state == 1: 38 | return True 39 | return False 40 | 41 | login_required() 42 | def update_statement(request): 43 | if request.method == 'POST': 44 | form = update_statement_form(request.POST) 45 | if form.is_valid(): 46 | print("here") 47 | descriptor_id = form.cleaned_data["descriptor"] 48 | new_text = form.cleaned_data["new_text"] 49 | state = form.cleaned_data["state"] 50 | print(state) 51 | descriptor = Record_descriptor.objects.filter(pk=descriptor_id) 52 | 53 | if len(descriptor) == 1 and check_text_values(new_text, state): 54 | can_modif = controllers.can_modify(descriptor[0].category.record, request.user, state) 55 | if can_modif: 56 | if state == 0: 57 | descriptor[0].draft_statement = new_text 58 | elif state == 1: 59 | descriptor[0].draft_statement = new_text 60 | else: 61 | descriptor[0].draft_statement = new_text 62 | descriptor[0].save() 63 | 64 | return JsonResponse({"error":False,"success":True}) 65 | 66 | 67 | return JsonResponse({"error":"Bad query","success":False}) 68 | 69 | 70 | login_required() 71 | def update_level(request): 72 | 73 | if request.method == 'POST': 74 | form = update_level_form(request.POST) 75 | if form.is_valid(): 76 | descriptor_id = form.cleaned_data["descriptor"] 77 | new_level = form.cleaned_data["new_level"] 78 | state = form.cleaned_data["state"] 79 | 80 | descriptor = Record_descriptor.objects.filter(pk=descriptor_id) 81 | 82 | if len(descriptor) == 1 and check_level_values(new_level, state): 83 | can_modif = controllers.can_modify(descriptor[0].category.record, request.user, state) 84 | if can_modif: 85 | if state == 0: 86 | descriptor[0].draft_level = new_level 87 | elif state == 1: 88 | descriptor[0].feedback_level = new_level 89 | else: 90 | descriptor[0].final_level = new_level 91 | descriptor[0].save() 92 | 93 | return JsonResponse({"error":False,"success":True}) 94 | 95 | 96 | return JsonResponse({"error":"Bad query","success":False}) -------------------------------------------------------------------------------- /chemtrack_app/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.0.5 on 2020-04-14 09:41 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Assignment', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('student_cid', models.CharField(max_length=15)), 20 | ('teacher_cid', models.CharField(max_length=15)), 21 | ], 22 | ), 23 | migrations.CreateModel( 24 | name='Category_template', 25 | fields=[ 26 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 27 | ('name', models.CharField(max_length=200)), 28 | ('description', models.TextField()), 29 | ], 30 | ), 31 | migrations.CreateModel( 32 | name='Descriptor_template', 33 | fields=[ 34 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 35 | ('name', models.CharField(max_length=200)), 36 | ('description', models.TextField()), 37 | ('order', models.IntegerField(default=0)), 38 | ('category_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chemtrack_app.Category_template')), 39 | ], 40 | ), 41 | migrations.CreateModel( 42 | name='Record', 43 | fields=[ 44 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 45 | ('date_creation', models.DateTimeField(auto_now_add=True)), 46 | ('student_cid', models.CharField(max_length=15)), 47 | ('state', models.IntegerField(default=0)), 48 | ('levels', models.CharField(max_length=200)), 49 | ], 50 | ), 51 | migrations.CreateModel( 52 | name='Record_category', 53 | fields=[ 54 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 55 | ('level', models.IntegerField(default=0)), 56 | ('record_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chemtrack_app.Record')), 57 | ('template_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chemtrack_app.Category_template')), 58 | ], 59 | ), 60 | migrations.CreateModel( 61 | name='Record_descriptor', 62 | fields=[ 63 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 64 | ('order', models.IntegerField(default=0)), 65 | ('draft_level', models.IntegerField(default=0)), 66 | ('feedback_level', models.IntegerField(default=0)), 67 | ('final_level', models.IntegerField(default=0)), 68 | ('draft_statement', models.TextField()), 69 | ('feedback_statement', models.TextField()), 70 | ('final_statement', models.TextField()), 71 | ('category_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chemtrack_app.Record_category')), 72 | ('record_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chemtrack_app.Record')), 73 | ('template_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='chemtrack_app.Descriptor_template')), 74 | ], 75 | ), 76 | ] 77 | -------------------------------------------------------------------------------- /chemtrack_app/templates/chemtrack_app/record_management/admin.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base.html" %} 2 | {% load record %} 3 | {% load groups %} 4 | 5 | {% block content %} 6 |

{{record.name}}

7 |

8 | Description: {{record.description}}
9 | Date: {{record.date}}
10 | Created the: {{record.date_creation}}
11 | Student: {{record.student}}, {{record.student.profile.cid}}
12 | State: 13 | {% if record.state is 0 %} 14 | Draft Stage 15 | {% elif record.state is 1 %} 16 | Waiting for feedback by teachers 17 | {% elif record.state is 2 %} 18 | Waiting for final submission by student 19 | {% elif record.state is 3%} 20 | Waiting for final confirmation by teachers 21 | {% else %} 22 | Final record 23 | {% endif %}
24 | Record ID: {{record.pk}} 25 | 26 |

27 | 28 | Go back

29 | 30 | {% if request.user|has_group:"student" %} 31 | {% if record.state is 0 %} 32 | Delete the record : 33 |
34 | 35 | {% csrf_token %} 36 | 37 |
38 | {% endif %} 39 | {% elif request.user|has_group:"teacher"%} 40 |
41 | Set the record to the draft phase : 42 | 43 | {% csrf_token %} 44 | 45 | 46 |

47 |
48 | Set the record to the feedback phase : 49 | 50 | {% csrf_token %} 51 | 52 | 53 |

54 | 55 |
56 | Set the record to the final statement phase : 57 | 58 | {% csrf_token %} 59 | 60 | 61 |

62 | 63 |
64 | Set the record to the waiting confirmation phase : 65 | 66 | {% csrf_token %} 67 | 68 | 69 |

70 | 71 |
72 | Set the record to the final record state : 73 | 74 | {% csrf_token %} 75 | 76 | 77 |

78 | {% endif %} 79 | 80 | {% endblock %} -------------------------------------------------------------------------------- /chemtrack/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for chemtrack project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.0.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | TEMPLATE_RECORD = "record_template/template.json" 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ 22 | 23 | # SECURITY WARNING: keep the secret key used in production secret! 24 | SECRET_KEY = 'c@vou8tx2tb=^ibg1==ilo$$a0#7cgj!v2m9_^mv77ec2hfgl8' 25 | 26 | # SECURITY WARNING: don't run with debug turned on in production! 27 | DEBUG = True 28 | 29 | ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'chemtrack_app.apps.ChemtrackAppConfig', 36 | 'django.contrib.auth', 37 | 'django.contrib.admin', 38 | 'django.contrib.contenttypes', 39 | 'django.contrib.sessions', 40 | 'django.contrib.messages', 41 | 'django.contrib.staticfiles', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'chemtrack.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'chemtrack.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/3.0/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/3.0/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 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/3.0/howto/static-files/ 121 | 122 | STATIC_URL = '/static/' 123 | 124 | 125 | CSRF_COOKIE_DOMAIN = None 126 | CSRF_COOKIE_SECURE = None 127 | LOGIN_REDIRECT_URL = '/' 128 | 129 | AUTH_PROFILE_MODULE = 'chemtrack_app.Profile' -------------------------------------------------------------------------------- /chemtrack_app/templates/chemtrack_app/teachers/teacher_search_student.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base.html" %} 2 | 3 | {% block content %} 4 |
5 | 6 | Student's CID : 7 | 8 | 9 | 10 |
11 | {% if search %} 12 | 13 | {% if found is False%} 14 | Student not found 15 | {% else%} 16 | 17 | Student: {{student}}
18 | CID: {{student.profile.cid}}
19 |

Drafts

20 | 21 | {% for record in drafts %} 22 | 23 |
24 |
25 |
Created the: {{record.date_creation}}
26 |
27 |
Title: {{record.name}}
28 |
Date: {{record.date}}
29 |
{{record.description|truncatewords:15}}
30 |
{{record.level }}
31 |
32 |
33 | {% endfor %} 34 | 35 |

Waiting for feedback

36 | 37 | {% for record in feedbacks %} 38 | 39 |
40 |
41 |
Created the: {{record.date_creation}}
42 |
43 |
Title: {{record.name}}
44 |
Date: {{record.date}}
45 |
{{record.description|truncatewords:15}}
46 |
{{record.level }}
47 |
48 |
49 | {% endfor %} 50 | 51 | 52 |

Final statement

53 | 54 | {% for record in final_statements %} 55 | 56 |
57 |
58 |
Created the: {{record.date_creation}}
59 |
60 |
Title: {{record.name}}
61 |
Date: {{record.date}}
62 |
{{record.description|truncatewords:15}}
63 |
{{record.level }}
64 |
65 |
66 | {% endfor %} 67 | 68 | 69 |

Waiting for final confirmation

70 | 71 | {% for record in confirmations %} 72 | 73 |
74 |
75 |
Created the: {{record.date_creation}}
76 |
77 |
Title: {{record.name}}
78 |
Date: {{record.date}}
79 |
{{record.description|truncatewords:15}}
80 |
{{record.level }}
81 |
82 |
83 | {% endfor %} 84 | 85 | 86 |

Final Records

87 | 88 | {% for record in records %} 89 | 90 |
91 |
92 |
Created the: {{record.date_creation}}
93 |
94 |
Title: {{record.name}}
95 |
Date: {{record.date}}
96 |
{{record.description|truncatewords:15}}
97 |
{{record.level }}
98 |
99 |
100 | {% endfor %} 101 | 102 | {% endif %} 103 | {% endif %} 104 | 105 | {% endblock %} -------------------------------------------------------------------------------- /chemtrack_app/static/css/base.css: -------------------------------------------------------------------------------- 1 | html { 2 | padding:0; 3 | background-color: rgb(240,240,240); 4 | } 5 | 6 | 7 | header { 8 | margin:0; 9 | background-color:rgb(50,80,1); 10 | padding:10px; 11 | } 12 | header h1 { 13 | margin:0; 14 | text-align : center; 15 | color:rgb(240,240,240); 16 | } 17 | #log_in_div { 18 | float:right; 19 | margin-top:5px; 20 | font-size: 25px; 21 | color:rgb(240,240,240); 22 | } 23 | #menu_div { 24 | float:left; 25 | margin-top:5px; 26 | font-size: 25px; 27 | color:rgb(240,240,240); 28 | font:bold; 29 | } 30 | #menu_div:hover { 31 | cursor:pointer; 32 | } 33 | header a { 34 | color: inherit; 35 | text-decoration: none; 36 | } 37 | body { 38 | margin:0; 39 | margin-top:0; 40 | } 41 | 42 | div#content { 43 | overflow:auto; 44 | padding:20px; 45 | max-width:900px; 46 | margin : auto; 47 | margin-top : 30px; 48 | font-size:18px; 49 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 50 | } 51 | /* The side navigation menu */ 52 | .sidenav { 53 | height: 100%; /* 100% Full-height */ 54 | width: 0; /* 0 width - change this with JavaScript */ 55 | position: fixed; /* Stay in place */ 56 | z-index: 1; /* Stay on top */ 57 | top: 0; /* Stay at the top */ 58 | left: 0; 59 | background-color: rgb(240,240,240); /* Black*/ 60 | border-right : black 1px solid; 61 | overflow-x: hidden; /* Disable horizontal scroll */ 62 | padding-top: 60px; /* Place content 60px from the top */ 63 | transition: 0.5s; /* 0.5 second transition effect to slide in the sidenav */ 64 | } 65 | 66 | /* The navigation menu links */ 67 | .sidenav a { 68 | padding: 8px 8px 8px 32px; 69 | text-decoration: none; 70 | font-size: 25px; 71 | color: rgb(50,80,1); 72 | display: block; 73 | transition: 0.3s; 74 | } 75 | 76 | /* When you mouse over the navigation links, change their color */ 77 | .sidenav a:hover { 78 | color: rgb(100,160,2); 79 | } 80 | 81 | /* Position and style the close button (top right corner) */ 82 | .sidenav .closebtn { 83 | position: absolute; 84 | top: 0; 85 | right: 25px; 86 | font-size: 36px; 87 | margin-left: 50px; 88 | } 89 | 90 | /* Style page content - use this if you want to push the page content to the right when you open the side navigation */ 91 | #main { 92 | transition: margin-left .5s; 93 | padding: 20px; 94 | } 95 | 96 | /* On smaller screens, where height is less than 450px, change the style of the sidenav (less padding and a smaller font size) */ 97 | @media screen and (max-height: 450px) { 98 | .sidenav {padding-top: 15px;} 99 | .sidenav a {font-size: 18px;} 100 | } 101 | 102 | /* Overlay when menu is open */ 103 | 104 | #overlay { 105 | width:100%; 106 | height:100%; 107 | position:absolute; 108 | top:-48px; 109 | display:none; 110 | background-color: rgb(240,240,240); 111 | opacity:0.8; 112 | } 113 | #overlay:hover{ 114 | cursor:pointer; 115 | } 116 | 117 | /* Outside of content buttons */ 118 | 119 | .wide_button{ 120 | padding-top: 5px; 121 | padding-bottom:5px; 122 | background-color:rgb(50,80,1); 123 | text-align:center; 124 | width:100%; 125 | font-size:20px; 126 | color:white; 127 | text-decoration: none; 128 | } 129 | 130 | .no_style_link { 131 | text-decoration: none; 132 | color:inherit; 133 | } 134 | 135 | .wide_button:hover{ 136 | cursor:pointer; 137 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 138 | } 139 | 140 | 141 | /* In main area button */ 142 | .in-button { 143 | padding-left:10px; 144 | padding-top: 5px; 145 | padding-bottom:5px; 146 | width:95%; 147 | font-size:20px; 148 | color:black; 149 | text-decoration: none; 150 | border: 1px rgb(50,80,1) solid; 151 | margin-bottom:10px; 152 | } 153 | 154 | .in-button:hover{ 155 | cursor:pointer; 156 | background-color:rgb(240,240,240); 157 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); 158 | } 159 | 160 | .in-button .right_info { 161 | float:right; 162 | } 163 | 164 | 165 | /* Draft Page presentation */ 166 | 167 | #side_menu { 168 | float:left; 169 | border:1px solid black; 170 | margin-right:30px; 171 | padding-top:0px; 172 | padding-bottom:10px; 173 | width:120px; 174 | } 175 | 176 | .cat_menu_element { 177 | padding-left:10px; 178 | padding-right:10px; 179 | font-size: 20px; 180 | color: rgb(50,80,1); 181 | transition: 0.3s; 182 | margin-top:10px; 183 | } 184 | 185 | 186 | .cat_menu_element:hover { 187 | color: rgb(100,160,2); 188 | cursor:pointer; 189 | } 190 | 191 | .cat_menu_element.selected { 192 | background-color:rgb(200,200,200); 193 | } 194 | 195 | #record_area{ 196 | margin-left:150px; 197 | } 198 | 199 | /* Descriptor styles */ 200 | 201 | div.level_button { 202 | border: 1px solid black; 203 | display:inline-block; 204 | text-align: center; 205 | margin:0; 206 | width:15px; 207 | } 208 | div.level_button:hover { 209 | cursor:pointer; 210 | } 211 | div.level_button.notclickable { 212 | border: 1px solid grey; 213 | cursor:default; 214 | color:grey; 215 | } 216 | 217 | div.level_button.selected { 218 | background-color: rgb(100,160,2); 219 | } 220 | 221 | /* Statements */ 222 | textarea.descriptor_text { 223 | width:100%; 224 | } 225 | 226 | /* Elements to display the different states */ 227 | .descriptor_display_choice { 228 | border: 1px solid black; 229 | display:inline-block; 230 | text-align: center; 231 | margin:0; 232 | width:100px; 233 | } 234 | div.descriptor_display_choice:hover { 235 | cursor:pointer; 236 | } 237 | .descriptor_display_choice.selected { 238 | background-color: rgb(100,160,2); 239 | } 240 | 241 | .descriptor_container { 242 | display:none; 243 | } 244 | .descriptor_container.selected { 245 | display:block; 246 | } 247 | 248 | /* Each category container */ 249 | .category_container{ 250 | display:none; 251 | } 252 | .category_container.selected { 253 | display:block; 254 | } 255 | 256 | /* Tooltip code */ 257 | 258 | .tooltip { 259 | position: relative; 260 | display: inline-block; 261 | } 262 | 263 | .tooltip .tooltiptext { 264 | visibility: hidden; 265 | 266 | width: 300px; 267 | background-color: rgb(50,80,1); 268 | color: #fff; 269 | text-align: center; 270 | border-radius: 6px; 271 | padding: 5px 0; 272 | 273 | /* Position the tooltip */ 274 | position: absolute; 275 | top: -5px; 276 | left: 105%; 277 | z-index: 1; 278 | } 279 | 280 | .tooltip:hover .tooltiptext { 281 | visibility: visible; 282 | } -------------------------------------------------------------------------------- /chemtrack_app/templates/chemtrack_app/record_management/record.html: -------------------------------------------------------------------------------- 1 | {% extends "../../base.html" %} 2 | {% load record %} 3 | {% load groups %} 4 | 5 | {% block content %} 6 |

{{record.name}}

7 |

8 | Description: {{record.description}}
9 | Date: {{record.date}}
10 | Created the: {{record.date_creation}}
11 | Student: {{record.student}}, {{record.student.profile.cid}}
12 | State: 13 | {% if record.state is 0 %} 14 | Draft Stage 15 | {% elif record.state is 1 %} 16 | Waiting for feedback by teachers 17 | {% elif record.state is 2 %} 18 | Waiting for final submission by student 19 | {% elif record.state is 3%} 20 | Waiting for final confirmation by teachers 21 | {% else %} 22 | Final record 23 | {% endif %} 24 |

25 | 26 |
27 | {% for category in record.record_category_set.all%} 28 |
34 | {{category.template.name}} 35 |
36 | {% endfor %} 37 | 38 |
39 |
Admin
40 |
41 | 42 | {% can_submit record user as can_submit_flag %} 43 | 44 | {% if can_submit_flag %} 45 |
46 |
47 |
48 | 49 | {% csrf_token %} 50 | 51 |
52 |
53 |
54 | {% endif %} 55 | 56 | 57 | 58 | 59 |
60 |
61 | 62 | 63 | {% for category in record.record_category_set.all %} 64 |
66 | 67 | 76 | 77 | 78 | 79 |
80 |

{{category.template.name}}

81 | {{category.template.description}} 82 |
83 |
84 | {% for descriptor in category.record_descriptor_set.all %} 85 | 86 |
87 |

{{descriptor.template.name}}

88 | {{descriptor.template.description}} 89 |
90 | 91 |
92 |
Draft
94 |
Feedback
96 |
Final
98 |
99 | 100 | {# Looping through the 3 states of the draft #} 101 | 102 | {% with 'Draft Feedback Final' as list %} 103 | {% for i in list.split %} 104 | 105 | {# Scripts for toggling the descriptors stages #} 106 | 113 | 114 | {# Compute if the user can modify this part of the descriptor #} 115 | {% can_modify record user forloop.counter0 as can_modify_flag %} 116 | 117 |
121 | 122 |
{{i}}
123 | 124 | {% list_levels_generate forloop.counter0 as list_levels %} 125 |
129 | {% for j in list_levels %} 130 | 131 |
135 | {{j}} 136 |
137 | 138 | {% if can_modify_flag %} 139 | 171 | {% endif %} 172 | {% endfor %} 173 |
174 | 175 |
176 | 177 | 181 | {% if can_modify_flag %} 182 | 212 | {% endif %} 213 |
214 | {% endfor %} 215 | {% endwith %} 216 | 217 |
218 | {% endfor %} 219 | 220 |
221 | {% endfor %} 222 | 223 | 224 |
225 | {% endblock %} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /chemtrack_app/static/js/jquery.js: -------------------------------------------------------------------------------- 1 | /*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),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-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|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(p.childNodes),p.childNodes),t[p.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&&(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&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$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[S]=!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:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),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=S,!C.getElementsByName||!C.getElementsByName(S).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){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),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("!=",F)}),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==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,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]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[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){N(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=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(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 D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.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 S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.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(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(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;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={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)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").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 Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.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(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.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?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
",2===Ut.childNodes.length),S.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=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.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,S.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):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.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"===S.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"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.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)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.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?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.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){S.fn[n]=function(e,t){return 0