├── EC ├── __init__.py ├── docker_settings.py ├── wsgi.py ├── urls.py └── settings.py ├── ecweb ├── __init__.py ├── tests │ ├── __init__.py │ ├── test_url.py │ ├── test_models.py │ ├── test_create_student_view.py │ └── test_create_user_type_view.py ├── migrations │ ├── __init__.py │ ├── 0005_classroom_slug.py │ ├── 0003_auto_20171227_0048.py │ ├── 0008_classroom_is_active.py │ ├── 0009_auto_20171230_1715.py │ ├── 0004_classroom_turn.py │ ├── 0007_auto_20171229_1943.py │ ├── 0011_auto_20180103_0243.py │ ├── 0006_auto_20171229_1918.py │ ├── 0002_createsuperuser.py │ ├── 0010_event.py │ └── 0001_initial.py ├── templates │ ├── registration │ │ ├── password_reset_subject.txt │ │ ├── logout.html │ │ ├── forget-complete.html │ │ ├── change-password.html │ │ ├── forget-done.html │ │ ├── password_reset_email.html │ │ ├── forget-password.html │ │ ├── forget-confirm.html │ │ ├── create_user.html │ │ ├── create_student.html │ │ └── login.html │ └── ecweb │ │ ├── home.html │ │ ├── base.html │ │ ├── events.html │ │ ├── classroom.html │ │ ├── classes.html │ │ ├── class_attendance.html │ │ ├── classroom │ │ ├── create_classroom.html │ │ ├── update_classroom.html │ │ ├── classroom_confirm_delete.html │ │ ├── classroom.html │ │ └── detail_classroom.html │ │ ├── teacher-dashboard.html │ │ ├── coordinator-dashboard.html │ │ ├── student.html │ │ ├── baseadmin.html │ │ └── student-dashboard.html ├── static │ ├── images │ │ ├── face.jpg │ │ ├── .DS_Store │ │ ├── favicon.png │ │ ├── flags │ │ │ ├── AD.png │ │ │ ├── AE.png │ │ │ ├── AG.png │ │ │ ├── AM.png │ │ │ ├── AR.png │ │ │ ├── AT.png │ │ │ ├── AU.png │ │ │ ├── BE.png │ │ │ ├── BF.png │ │ │ ├── BG.png │ │ │ ├── BO.png │ │ │ ├── BR.png │ │ │ ├── CA.png │ │ │ ├── CD.png │ │ │ ├── CG.png │ │ │ ├── CH.png │ │ │ ├── CL.png │ │ │ ├── CM.png │ │ │ ├── CN.png │ │ │ ├── CO.png │ │ │ ├── CZ.png │ │ │ ├── DE.png │ │ │ ├── DJ.png │ │ │ ├── DK.png │ │ │ ├── DZ.png │ │ │ ├── EE.png │ │ │ ├── EG.png │ │ │ ├── ES.png │ │ │ ├── FI.png │ │ │ ├── FR.png │ │ │ ├── GA.png │ │ │ ├── GB.png │ │ │ ├── GM.png │ │ │ ├── GT.png │ │ │ ├── HN.png │ │ │ ├── HT.png │ │ │ ├── HU.png │ │ │ ├── ID.png │ │ │ ├── IE.png │ │ │ ├── IL.png │ │ │ ├── IN.png │ │ │ ├── IQ.png │ │ │ ├── IR.png │ │ │ ├── IT.png │ │ │ ├── JM.png │ │ │ ├── JO.png │ │ │ ├── JP.png │ │ │ ├── KG.png │ │ │ ├── KN.png │ │ │ ├── KP.png │ │ │ ├── KR.png │ │ │ ├── KW.png │ │ │ ├── KZ.png │ │ │ ├── LA.png │ │ │ ├── LB.png │ │ │ ├── LC.png │ │ │ ├── LS.png │ │ │ ├── LU.png │ │ │ ├── LV.png │ │ │ ├── MG.png │ │ │ ├── MK.png │ │ │ ├── ML.png │ │ │ ├── MM.png │ │ │ ├── MT.png │ │ │ ├── MX.png │ │ │ ├── NA.png │ │ │ ├── NE.png │ │ │ ├── NG.png │ │ │ ├── NI.png │ │ │ ├── NL.png │ │ │ ├── NO.png │ │ │ ├── OM.png │ │ │ ├── PA.png │ │ │ ├── PE.png │ │ │ ├── PG.png │ │ │ ├── PK.png │ │ │ ├── PL.png │ │ │ ├── PT.png │ │ │ ├── PY.png │ │ │ ├── QA.png │ │ │ ├── RO.png │ │ │ ├── RU.png │ │ │ ├── RW.png │ │ │ ├── SA.png │ │ │ ├── SE.png │ │ │ ├── SG.png │ │ │ ├── SL.png │ │ │ ├── SN.png │ │ │ ├── SO.png │ │ │ ├── SV.png │ │ │ ├── TD.png │ │ │ ├── TJ.png │ │ │ ├── TL.png │ │ │ ├── TR.png │ │ │ ├── TZ.png │ │ │ ├── UA.png │ │ │ ├── US.png │ │ │ ├── VE.png │ │ │ ├── VN.png │ │ │ └── YE.png │ │ ├── icons │ │ │ ├── 1.png │ │ │ ├── 10.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ ├── profile.jpg │ │ ├── logo_star_mini.jpg │ │ ├── logo_star_black.png │ │ ├── star-admin-logo.png │ │ ├── logo_star_black.svg │ │ └── logo_star.svg │ ├── css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ └── libs │ │ │ ├── cropper.min.css │ │ │ └── perfect-scrollbar.min.css │ └── js │ │ ├── off-canvas.js │ │ ├── maps.js │ │ ├── misc.js │ │ ├── hoverable-collapse.js │ │ ├── chart.js │ │ └── libs │ │ ├── tether.min.js │ │ └── perfect-scrollbar.min.js ├── apps.py ├── utils │ └── li.py ├── urls.py ├── admin.py ├── forms.py ├── models.py └── views.py ├── media └── avatars │ ├── bot.jpeg │ └── user_default.png ├── .travis.yml ├── provision ├── docker │ ├── wait-for-it.sh │ └── Dockerfile └── compose │ └── docker-compose.yml ├── req_dev.txt ├── manage.py ├── LICENSE ├── .gitignore └── README.md /EC/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecweb/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecweb/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecweb/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ecweb/templates/registration/password_reset_subject.txt: -------------------------------------------------------------------------------- 1 | TestSite password reset -------------------------------------------------------------------------------- /media/avatars/bot.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/media/avatars/bot.jpeg -------------------------------------------------------------------------------- /ecweb/static/images/face.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/face.jpg -------------------------------------------------------------------------------- /ecweb/static/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/.DS_Store -------------------------------------------------------------------------------- /media/avatars/user_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/media/avatars/user_default.png -------------------------------------------------------------------------------- /ecweb/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class EcwebConfig(AppConfig): 5 | name = 'ecweb' 6 | -------------------------------------------------------------------------------- /ecweb/static/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/favicon.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/AD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/AD.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/AE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/AE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/AG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/AG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/AM.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/AR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/AR.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/AT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/AT.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/AU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/AU.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/BE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/BE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/BF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/BF.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/BG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/BG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/BO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/BO.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/BR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/BR.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CD.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CH.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CL.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CM.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CN.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CO.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/CZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/CZ.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/DE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/DJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/DJ.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/DK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/DK.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/DZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/DZ.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/EE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/EE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/EG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/EG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/ES.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/ES.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/FI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/FI.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/FR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/FR.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/GA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/GA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/GB.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/GM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/GM.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/GT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/GT.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/HN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/HN.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/HT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/HT.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/HU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/HU.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/ID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/ID.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/IE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/IE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/IL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/IL.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/IN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/IN.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/IQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/IQ.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/IR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/IR.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/IT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/IT.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/JM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/JM.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/JO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/JO.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/JP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/JP.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/KG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/KG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/KN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/KN.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/KP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/KP.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/KR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/KR.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/KW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/KW.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/KZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/KZ.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/LA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/LA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/LB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/LB.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/LC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/LC.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/LS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/LS.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/LU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/LU.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/LV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/LV.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/MG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/MG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/MK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/MK.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/ML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/ML.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/MM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/MM.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/MT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/MT.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/MX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/MX.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/NA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/NA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/NE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/NE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/NG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/NG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/NI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/NI.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/NL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/NL.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/NO.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/OM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/OM.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/PA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/PA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/PE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/PE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/PG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/PG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/PK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/PK.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/PL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/PL.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/PT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/PT.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/PY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/PY.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/QA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/QA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/RO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/RO.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/RU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/RU.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/RW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/RW.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/SA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/SA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/SE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/SE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/SG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/SG.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/SL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/SL.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/SN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/SN.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/SO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/SO.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/SV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/SV.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/TD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/TD.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/TJ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/TJ.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/TL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/TL.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/TR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/TR.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/TZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/TZ.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/UA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/UA.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/US.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/VE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/VE.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/VN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/VN.png -------------------------------------------------------------------------------- /ecweb/static/images/flags/YE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/flags/YE.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/1.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/10.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/2.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/3.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/4.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/5.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/6.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/7.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/8.png -------------------------------------------------------------------------------- /ecweb/static/images/icons/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/icons/9.png -------------------------------------------------------------------------------- /ecweb/static/images/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/profile.jpg -------------------------------------------------------------------------------- /ecweb/static/css/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/css/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /ecweb/static/images/logo_star_mini.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/logo_star_mini.jpg -------------------------------------------------------------------------------- /ecweb/templates/ecweb/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/base.html' %} 2 | 3 | {% block content %} 4 | Home 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /ecweb/static/images/logo_star_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/logo_star_black.png -------------------------------------------------------------------------------- /ecweb/static/images/star-admin-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/images/star-admin-logo.png -------------------------------------------------------------------------------- /ecweb/static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /ecweb/static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /ecweb/static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /ecweb/static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesperes-zz/EC/HEAD/ecweb/static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /ecweb/static/js/off-canvas.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(function() { 3 | $('[data-toggle="offcanvas"]').on("click", function () { 4 | $('.row-offcanvas').toggleClass('active') 5 | }); 6 | }); 7 | })(jQuery); 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | - "nightly" 5 | # command to install dependencies 6 | install: 7 | - pip install -r req_dev.txt 8 | # command to run tests 9 | script: 10 | - python manage.py test -------------------------------------------------------------------------------- /ecweb/static/js/maps.js: -------------------------------------------------------------------------------- 1 | var map; 2 | function initMap() { 3 | map = new google.maps.Map(document.getElementById('map'), { 4 | center: {lat: -34.397, lng: 150.644}, 5 | zoom: 8 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /ecweb/templates/registration/logout.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'ecweb/base.html' %} 3 | 4 | {% block content %} 5 | 6 |

Logged out!

7 | 8 | Click here to login again. 9 | 10 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/templates/registration/forget-complete.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'ecweb/base.html' %} 3 | 4 | {% block content %} 5 |

6 | Your password has been set. You may go ahead and sign in now. 7 |

8 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/templates/ecweb/base.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | English College 6 | {% block head %}{% endblock %} 7 | 8 | 9 | {% block content %}{% endblock %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /ecweb/templates/registration/change-password.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% block content %} 4 |

Forgot password123

5 |
6 | {% csrf_token %} 7 | {{ form.as_p }} 8 | 9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /EC/docker_settings.py: -------------------------------------------------------------------------------- 1 | DATABASES = { 2 | 'default': { 3 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 4 | 'NAME': 'ec_development', 5 | 'USER': 'ec_development', 6 | 'HOST': 'db', 7 | 'PORT': 5432, 8 | 'PASSWORD': '98uonqwnepj021' 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ecweb/static/js/misc.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(function() { 3 | $('#sidebar .nav').perfectScrollbar(); 4 | $('.container-scroller').perfectScrollbar( {suppressScrollX: true}); 5 | $('[data-toggle="minimize"]').on("click", function () { 6 | $('body').toggleClass('sidebar-icon-only'); 7 | }); 8 | }); 9 | })(jQuery); 10 | -------------------------------------------------------------------------------- /provision/docker/wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # wait-for-postgres.sh 3 | 4 | set -e 5 | 6 | host="$1" 7 | shift 8 | cmd="$@" 9 | 10 | until psql -h "$host" -U "ec_development" --dbname "ec_development" -c '\l'; do 11 | >&2 echo "Postgres is unavailable - sleeping" 12 | sleep 1 13 | done 14 | 15 | >&2 echo "Postgres is up - executing command" 16 | exec $cmd 17 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/events.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block content %} 5 |
6 |


Events ---
7 | 8 | 9 | 10 | {% for event in events %} 11 |

12 | 13 | {{event.title}} 14 | 15 | 16 |

17 | {% endfor %} 18 | 19 |
20 | {% endblock %} -------------------------------------------------------------------------------- /req_dev.txt: -------------------------------------------------------------------------------- 1 | decorator==4.1.2 2 | Django==2.0 3 | docopt==0.6.2 4 | ipython==6.2.1 5 | ipython-genutils==0.2.0 6 | jedi==0.11.0 7 | olefile==0.44 8 | parso==0.1.0 9 | pexpect==4.3.0 10 | pickleshare==0.7.4 11 | Pillow==4.3.0 12 | prompt-toolkit==1.0.15 13 | ptpython==0.41 14 | ptyprocess==0.5.2 15 | Pygments==2.2.0 16 | pytz==2017.3 17 | simplegeneric==0.8.1 18 | six==1.11.0 19 | traitlets==4.3.2 20 | wcwidth==0.1.7 21 | psycopg2 22 | geocoder==1.33.0 23 | -------------------------------------------------------------------------------- /EC/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for EC project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "EC.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /ecweb/templates/registration/forget-done.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'ecweb/base.html' %} 3 | 4 | {% block content %} 5 |

6 | We've emailed you instructions for setting your password, if an account exists with the email you entered. 7 | You should receive them shortly. 8 |

9 |

10 | If you don't receive an email, please make sure you've entered the address you registered with, 11 | and check your spam folder. 12 |

13 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/templates/registration/password_reset_email.html: -------------------------------------------------------------------------------- 1 | {% autoescape off %} 2 | To initiate the password reset process for your {{ user.get_username }} TestSite Account, 3 | click the link below: 4 | 5 | {{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %} 6 | 7 | If clicking the link above doesn't work, please copy and paste the URL in a new browser 8 | window instead. 9 | 10 | Sincerely, 11 | The TestSite Team 12 | {% endautoescape %} -------------------------------------------------------------------------------- /ecweb/utils/li.py: -------------------------------------------------------------------------------- 1 | """ List of CHOICES""" 2 | 3 | 4 | level_choices = ( 5 | ('Beginner', 'Beginner'), 6 | ('Elementary', 'Elementary'), 7 | ) 8 | 9 | 10 | type_list = ( 11 | ('1-month', '1-month'), 12 | ('6-month', '6-month'), 13 | ) 14 | 15 | test_choices = ( 16 | ('listening', 'Listening'), 17 | ('reading', 'Reading'), 18 | ) 19 | 20 | classroom_turns_choices = ( 21 | ('morning', 'Morning'), 22 | ('afternoon', 'Afternoon') 23 | ) 24 | -------------------------------------------------------------------------------- /ecweb/migrations/0005_classroom_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-29 18:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0004_classroom_turn'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='classroom', 15 | name='slug', 16 | field=models.SlugField(blank=True, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /ecweb/migrations/0003_auto_20171227_0048.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-27 00:48 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0002_createsuperuser'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='coordinator', 15 | options={'permissions': (('view_all_classrooms', 'Can view all classrooms'),)}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /ecweb/migrations/0008_classroom_is_active.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-30 12:14 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0007_auto_20171229_1943'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='classroom', 15 | name='is_active', 16 | field=models.BooleanField(default=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /ecweb/templates/registration/forget-password.html: -------------------------------------------------------------------------------- 1 | {% extends "ecweb/base.html" %} 2 | {% block content %} 3 |
4 |
5 |

Reset your Password

6 |
{% csrf_token %} 7 | {{ form.as_p }} 8 | 9 | 10 |
11 |
12 |
13 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/templates/ecweb/classroom.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %}{% endblock %} 5 | 6 | {% block js %}{% endblock %} 7 | 8 | 9 | {% block content %} 10 |
11 | 12 | 13 | {% for classroom in classrooms%} 14 |

{{classroom.level}} - {{classroom.number_class}}

15 | link 16 | {% endfor%} 17 |
18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /ecweb/migrations/0009_auto_20171230_1715.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-30 17:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0008_classroom_is_active'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='classroom', 15 | name='number_class', 16 | field=models.PositiveIntegerField(blank=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/classes.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %}{% endblock %} 5 | 6 | {% block js %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |

Classes

11 | 12 | {% for class_obj in all_classes%} 13 |

{{class_obj.classroom.level}} - 14 | 15 | {{class_obj.date}} 16 |

17 | {% endfor %} 18 |
19 | {% endblock %} 20 | -------------------------------------------------------------------------------- /ecweb/templates/registration/forget-confirm.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'ecweb/base.html' %} 3 | 4 | {% block content %} 5 | {% if validlink %} 6 |

Change password

7 |
8 | {% csrf_token %} 9 | {{ form.as_p }} 10 | 11 |
12 | {% else %} 13 |

14 | The password reset link was invalid, possibly because it has already been used. 15 | Please request a new password reset. 16 |

17 | {% endif %} 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /ecweb/migrations/0004_classroom_turn.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-29 18:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0003_auto_20171227_0048'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='classroom', 15 | name='turn', 16 | field=models.CharField(choices=[('morning', 'Morning'), ('afternoon', 'Afternoon')], default=1, max_length=50), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /ecweb/migrations/0007_auto_20171229_1943.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-29 19:43 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0006_auto_20171229_1918'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='coordinator', 15 | options={'permissions': (('view_all_classrooms', 'Can view all classrooms'),)}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='teacher', 19 | options={}, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/class_attendance.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block js %} 10 | 11 | 12 | {% endblock %} 13 | 14 | 15 | 16 | 17 | {% block content %} 18 |
19 | 20 | Date: {{ class_obj.date }} 21 |
22 | Lessons: {{ class_obj.lesson }} 23 | 24 |
25 | {% csrf_token %} 26 | {{ form }} 27 | 28 |
29 |
30 | {% endblock %} 31 | -------------------------------------------------------------------------------- /ecweb/migrations/0011_auto_20180103_0243.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-01-03 02:43 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0010_event'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='event', 15 | name='end_event', 16 | field=models.DateTimeField(blank=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='event', 20 | name='start_event', 21 | field=models.DateTimeField(), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /ecweb/tests/test_url.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.urls import resolve 3 | 4 | 5 | from ecweb import views 6 | 7 | 8 | class UrlsTest(TestCase): 9 | def test_root_url_resolves_to_home_view(self): 10 | found = resolve('/') 11 | self.assertEqual(found.func, views.home_dashboard) 12 | 13 | def test_home_page_not_loged_returns_login_html(self): 14 | response = self.client.get('/', follow=True) 15 | self.assertTemplateUsed(response, 'registration/login.html') 16 | 17 | def test_student_resolve_user_detail_view(self): 18 | found = resolve('/student/') 19 | self.assertEqual(found.func, views.user_detail) 20 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/classroom/create_classroom.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %}{% endblock %} 5 | 6 | {% block js %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 | {% for message in messages %} 11 | {{ message }} 12 | {% endfor %} 13 |
14 | {% csrf_token %} 15 |

Create a classroom

16 | {% for field in form %} 17 |
18 | {{field.label_tag}}{{field}} 19 |
20 | {% endfor %} 21 | 22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/classroom/update_classroom.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %}{% endblock %} 5 | 6 | {% block js %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 | {% for message in messages %} 11 | {{ message }} 12 | {% endfor %} 13 |
14 | {% csrf_token %} 15 |

Update the {{object}} classroom

16 | {% for field in form %} 17 |
18 | {{field.label_tag}}{{field}} 19 |
20 | {% endfor %} 21 | 22 |
23 |
24 | {% endblock %} -------------------------------------------------------------------------------- /provision/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | MAINTAINER Rondineli Gomes 4 | 5 | USER root 6 | 7 | RUN apt-get update && apt-get upgrade -yq 8 | 9 | RUN apt-get install python3-dev python3-pip postgresql-client postgresql-client-common -yq 10 | 11 | RUN mkdir /src 12 | 13 | ENV PYTHONPATH = /src/ 14 | 15 | VOLUME /src 16 | 17 | ADD . /src 18 | 19 | WORKDIR /src 20 | 21 | RUN GIT_TRACE=1 pip3 install --exists-action s -r req_dev.txt 22 | 23 | RUN cp -rf /src/provision/docker/wait-for-it.sh /usr/bin/ 24 | 25 | RUN chmod +x /usr/bin/wait-for-it.sh 26 | 27 | RUN python3 manage.py migrate 28 | 29 | CMD ["/usr/bin/python3", "manage.py", "runserver", "0.0.0.0:8080"] 30 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/teacher-dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | {% endblock %} 6 | 7 | {% block js %} 8 | {% endblock %} 9 | 10 | {% block content %} 11 |
12 | 13 |

Teacher's Dashboard

14 |
15 |

I'm a teacher

16 |
17 |
18 | 25 | {% endblock %} 26 | 27 | 28 | {% block user_title %} 29 |

Teacher

30 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/templates/registration/create_user.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | 3 | {% block head %} 4 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 |
18 |
19 | {% csrf_token %} 20 | {{ form.non_field_errors }} 21 | {% for field in form %} 22 |
23 | {{ field.label }}: 24 | {{ field }} 25 | {{ field.errors }} 26 |
27 | {% endfor %} 28 | 29 |
30 |
31 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/migrations/0006_auto_20171229_1918.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-29 19:18 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('ecweb', '0005_classroom_slug'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='coordinator', 15 | options={'permissions': (('view_all_classrooms', 'Can view all classrooms'), ('view_classroom_detail', 'Can view the classroom detail'))}, 16 | ), 17 | migrations.AlterModelOptions( 18 | name='teacher', 19 | options={'permissions': (('view_classroom_detail', 'Can view the classroom detail'),)}, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /ecweb/migrations/0002_createsuperuser.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-24 04:04 2 | 3 | from django.utils import timezone 4 | from django.db import migrations 5 | from ecweb.models import BasicUser 6 | 7 | def create_superuser(apps, schema_editor): 8 | superuser = BasicUser() 9 | superuser.is_active = True 10 | superuser.is_superuser = True 11 | superuser.is_staff = True 12 | superuser.date_joined = timezone.now() 13 | superuser.username = 'admin' 14 | superuser.email = 'admin@admin.com' 15 | superuser.set_password('admin123') 16 | superuser.save() 17 | 18 | 19 | class Migration(migrations.Migration): 20 | dependencies = [ 21 | ('ecweb', '0001_initial'), 22 | ] 23 | 24 | operations = [ 25 | migrations.RunPython(create_superuser) 26 | ] 27 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "EC.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/classroom/classroom_confirm_delete.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %}{% endblock %} 5 | 6 | {% block js %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 | {% csrf_token %} 12 |
13 |
14 |

Confirm delete the {{object}} classroom ?

15 |
16 |
17 |
18 | Leave 19 |
20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 | {% endblock %} -------------------------------------------------------------------------------- /provision/compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | ec_app: 4 | build: 5 | context: ../../. 6 | dockerfile: provision/docker/Dockerfile 7 | restart: always 8 | ports: 9 | - 8080:8080 10 | command: bash -c "/usr/bin/wait-for-it.sh db sleep 5; python3 /src/manage.py migrate && python3 /src/manage.py runserver 0.0.0.0:8080" 11 | 12 | environment: 13 | - DOCKER_DEVELOPMENT=1 14 | - PGPASSWORD=98uonqwnepj021 15 | 16 | volumes: 17 | - ../../.:/src 18 | 19 | depends_on: 20 | - db 21 | 22 | links: 23 | - db:db 24 | 25 | db: 26 | image: postgres 27 | restart: always 28 | 29 | ports: 30 | - 5432:5432 31 | environment: 32 | - POSTGRES_USER=ec_development 33 | - POSTGRES_PASSWORD=98uonqwnepj021 34 | - POSTGRES_DB=ec_development 35 | 36 | -------------------------------------------------------------------------------- /ecweb/templates/registration/create_student.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | 3 | {% block head %} 4 | 14 | {% endblock %} 15 | 16 | {% block content %} 17 |
18 |
19 | {% csrf_token %} 20 | 21 | {{ userform.non_field_errors }} 22 | {{ studentform.non_field_errors }} 23 | 24 | {% for field in userform %} 25 |
26 | {{ field.label }}: 27 | {{ field }} 28 | {{ field.errors }} 29 |
30 | {% endfor %} 31 | 32 | {% for field in studentform %} 33 |
34 | {{ field.label }}: 35 | {{ field }} 36 | {{ field.errors }} 37 |
38 | {% endfor %} 39 | 40 | 41 |
42 |
43 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'ecweb/base.html' %} 3 | 4 | {% block content %} 5 | 6 | {% if form.errors %} 7 |

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

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

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

14 | {% else %} 15 |

Please login to see this page.

16 | {% endif %} 17 | {% endif %} 18 | 19 |
20 | {% csrf_token %} 21 | 22 |
23 | {{ form.username.label_tag }} 24 | {{ form.username }} 25 |
26 |
27 | {{ form.password.label_tag }} 28 | {{ form.password }} 29 |
30 | 31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |

Lost password?

39 | 40 | {% endblock %} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 James 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/classroom/classroom.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %}{% endblock %} 5 | 6 | {% block js %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 | {% if perms.ecweb.view_all_classrooms %} 11 | 12 | Create a classroom 13 | 14 | {% endif %} 15 |

16 | {% if perms.ecweb.view_all_classrooms %} 17 |

All Classrooms

18 | {% else %} 19 |

20 | {% with total_classrooms=object_list.count %} 21 | My Classroom{{ total_classrooms|pluralize }} 22 | {% endwith %} 23 |

24 | {% endif %} 25 |

26 |
27 | {% for classroom in object_list %} 28 | 35 | {% endfor %} 36 |
37 |
38 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/migrations/0010_event.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-01-03 02:42 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('ecweb', '0009_auto_20171230_1715'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Event', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('description', models.TextField(max_length=500)), 19 | ('title', models.CharField(max_length=50)), 20 | ('start_event', models.DateField()), 21 | ('end_event', models.DateField(blank=True)), 22 | ('address', models.CharField(max_length=250)), 23 | ('neighborhood', models.CharField(max_length=250)), 24 | ('city', models.CharField(max_length=250)), 25 | ('local_lat', models.CharField(blank=True, max_length=250, null=True)), 26 | ('local_long', models.CharField(blank=True, max_length=250, null=True)), 27 | ('limit', models.IntegerField(blank=True)), 28 | ('teacher', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, to='ecweb.Teacher')), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /ecweb/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | from django.urls import path 3 | 4 | from . import views 5 | 6 | 7 | urlpatterns = [ 8 | url(r'^$', views.home_dashboard), 9 | url(r'^dashboard/$', views.home_dashboard, name='home_dashboard'), 10 | url(r'^events/$', views.events_list_view, name='events_list_view'), 11 | url( 12 | r'^create-student/$', 13 | views.create_student_view, 14 | name='create-student' 15 | ), 16 | url( 17 | r'^create-user/(?P[-\w]+)/$', 18 | views.create_user_type_view, 19 | name='create-user' 20 | ), 21 | url(r'^student/', views.user_detail, name='user_detail'), 22 | url(r'^classrooms/$', views.ClassRoomListView.as_view(), name='classroom_view'), 23 | path('classrooms//detail', views.ClassRoomDetailView.as_view(), name='classroom_detail_view'), 24 | path('classrooms//edit', views.ClassRoomUpdateView.as_view(), name='classroom_update_view'), 25 | path('classrooms//delete', views.ClassRoomDeactivateView.as_view(), name='classroom_delete_view'), 26 | url(r'^classrooms/create/$', views.ClassRoomCreateView.as_view(), name='classroom_create_view'), 27 | url(r'^logout/$', views.logout_view), 28 | url( 29 | r'^class/(?P[0-9]+)/$', 30 | views.list_classes_view, 31 | name='list_classes_view' 32 | ), 33 | url( 34 | r'^class/(?P[0-9]+)/attendances/$', 35 | views.class_view, 36 | name='class_view' 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | dist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | *.sqlite3 104 | /media/avatars/profiles 105 | 106 | # pycharm 107 | .idea -------------------------------------------------------------------------------- /ecweb/templates/ecweb/coordinator-dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | 34 | {% endblock %} 35 | 36 | {% block js %} 37 | {% endblock %} 38 | 39 | {% block content %} 40 |
41 |

Coordinator's Dashboard

42 | 48 | 54 | 60 |
61 |
62 |
63 | 64 | Star Admin © 2017 65 | 66 |
67 |
68 | {% endblock %} 69 | 70 | {% block user_title %} 71 |

Coordinator

72 | {% endblock %} 73 | -------------------------------------------------------------------------------- /EC/urls.py: -------------------------------------------------------------------------------- 1 | """EC URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.urls import include, path 18 | from django.conf import settings 19 | from django.contrib import admin 20 | from django.conf.urls.static import static 21 | from django.contrib.auth import views as auth_views 22 | 23 | urlpatterns = [ 24 | path('', include('ecweb.urls')), 25 | url(r'^accounts/', include('django.contrib.auth.urls')), 26 | url(r'^admin/', admin.site.urls), 27 | path('change-password/', auth_views.PasswordChangeView.as_view( 28 | template_name='registration/change-password.html'),), 29 | path('forget-password/done', auth_views.PasswordResetDoneView.as_view( 30 | template_name='registration/forget-done.html'),), 31 | path('forget-password/', auth_views.PasswordResetView.as_view( 32 | template_name='registration/forget-password.html', 33 | success_url='done'), name='forget_password'), 34 | url(r'^password/reset/\ 35 | (?P[0-9A-Za-z_\-]+)/\ 36 | (?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', 37 | auth_views.PasswordResetConfirmView.as_view( 38 | template_name='registration/forget-confirm.html',), 39 | name='password_reset_confirm', 40 | ), 41 | url(r'^password/reset/complete/$', 42 | auth_views.PasswordResetCompleteView.as_view( 43 | template_name='registration/forget-complete.html'), 44 | name='password_reset_complete'), 45 | 46 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 47 | -------------------------------------------------------------------------------- /ecweb/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.forms import CheckboxSelectMultiple 3 | 4 | from .forms import CreateUserForm, UpdateUserFormAdmin 5 | from .models import (ClassRoom, Teacher, Student, Coordinator, 6 | Youtube, PdfFile, Class, BasicUser, Event) 7 | 8 | 9 | class BasicUserAdmin(admin.ModelAdmin): 10 | list_display = ( 11 | 'first_name', 'last_name', 12 | 'email', 13 | ) 14 | date_hierarchy = 'date_joined' 15 | search_fields = ( 16 | 'first_name', 'last_name', 'email', 17 | 'date_joined', 18 | ) 19 | list_filter = ('date_joined',) 20 | add_form = CreateUserForm 21 | form = UpdateUserFormAdmin 22 | 23 | def get_form(self, request, obj=None, **kwargs): 24 | defaults = {} 25 | if obj is None: 26 | defaults['form'] = self.add_form 27 | defaults.update(kwargs) 28 | return super().get_form(request, obj, **defaults) 29 | 30 | 31 | class CalendarAdmin(admin.ModelAdmin): 32 | pass 33 | 34 | 35 | class MenssageAdmin(admin.ModelAdmin): 36 | pass 37 | 38 | 39 | class YoutubeAdmin(admin.ModelAdmin): 40 | pass 41 | 42 | 43 | class PdfFileAdmin(admin.ModelAdmin): 44 | pass 45 | 46 | 47 | class ClassRoomAdmin(admin.ModelAdmin): 48 | pass 49 | 50 | 51 | class ClassAdmin(admin.ModelAdmin): 52 | def get_form(self, request, obj=None, **kwargs): 53 | form = super(ClassAdmin, self).get_form(request, obj, **kwargs) 54 | form.base_fields['attendances'].widget = CheckboxSelectMultiple() 55 | return form 56 | 57 | 58 | class StudentAdmin(admin.ModelAdmin): 59 | pass 60 | 61 | 62 | class TeacherAdmin(admin.ModelAdmin): 63 | pass 64 | 65 | 66 | class CoordinatorAdmin(admin.ModelAdmin): 67 | pass 68 | 69 | class EventAdmin(admin.ModelAdmin): 70 | pass 71 | 72 | 73 | admin.site.register(Event, EventAdmin) 74 | admin.site.register(BasicUser, BasicUserAdmin) 75 | admin.site.register(ClassRoom, ClassRoomAdmin) 76 | admin.site.register(Teacher, TeacherAdmin) 77 | admin.site.register(Youtube, YoutubeAdmin) 78 | admin.site.register(PdfFile, PdfFileAdmin) 79 | admin.site.register(Class, ClassAdmin) 80 | admin.site.register(Student, StudentAdmin) 81 | admin.site.register(Coordinator, CoordinatorAdmin) -------------------------------------------------------------------------------- /ecweb/tests/test_models.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from ecweb.models import ( 3 | ClassRoom, 4 | BasicUser, 5 | Student, 6 | Teacher 7 | ) 8 | 9 | 10 | class ClassRoomModelTest(TestCase): 11 | def setUp(self): 12 | self.classroom = ClassRoom.objects.create( 13 | number_class='01', 14 | level="upper" 15 | ) 16 | self.BasicUser = BasicUser.objects.create( 17 | email="test@ec.com", 18 | first_name="TestName", 19 | last_name="TestLastName" 20 | ) 21 | 22 | def test_create(self): 23 | self.assertTrue(ClassRoom.objects.exists()) 24 | 25 | def test_int_number_class(self): 26 | number = 1 27 | self.assertEqual(number, int(self.classroom.number_class)) 28 | 29 | def test_str_level(self): 30 | self.assertEqual('upper', str(self.classroom.level)) 31 | 32 | def test_student_forengkey(self): 33 | self.student = Student.objects.create( 34 | user=self.BasicUser, 35 | classroom=self.classroom, 36 | cod=1 37 | ) 38 | self.assertTrue(Student.objects.exists()) 39 | 40 | def test_teacher_forengkey(self): 41 | self.teacher = Teacher.objects.create( 42 | user=self.BasicUser 43 | ) 44 | self.assertTrue(Teacher.objects.exists()) 45 | 46 | 47 | class BasicUserModelTest(TestCase): 48 | def setUp(self): 49 | self.basic_user = BasicUser.objects.create( 50 | email="test@ec.com", 51 | password="abcd1234ec", 52 | first_name="TestName", 53 | last_name="TestLastName" 54 | ) 55 | self.student = Student.objects.create(user=self.basic_user, 56 | cod=1, 57 | type_of_course='1-month' 58 | ) 59 | 60 | def test_create(self): 61 | self.assertTrue(BasicUser.objects.exists()) 62 | 63 | def test_student_exists(self): 64 | self.assertTrue(Student.objects.exists()) 65 | 66 | def test_is_instance_of_BasicUser(self): 67 | self.assertIsInstance(self.basic_user, BasicUser) 68 | 69 | def test_default_image_profile(self): 70 | avatar_image_url = self.basic_user.avatar 71 | self.assertEqual('avatars/user_default.png', avatar_image_url) 72 | -------------------------------------------------------------------------------- /ecweb/tests/test_create_student_view.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.shortcuts import resolve_url as r 3 | from ecweb.models import ( 4 | BasicUser, 5 | Student, 6 | Coordinator 7 | ) 8 | from ecweb.forms import CreateUserForm, StudentForm 9 | 10 | 11 | class TestCreateStudentView(TestCase): 12 | """ Testing createuser system """ 13 | 14 | def setUp(self): 15 | user = BasicUser.objects.create_superuser( 16 | username='admin', 17 | password='1234abc', 18 | email="admin_test@admin.com" 19 | ) 20 | Coordinator.objects.create(user=user) 21 | self.data = { 22 | 'first_name': 'admin', 23 | 'last_name': 'test', 24 | 'email': 'admin_test3@test.com', 25 | 'password': '123456ab', 26 | 'confirm_password': '123456ab', 27 | 'cod': 1, 28 | 'type_of_course': '1-month' 29 | } 30 | self.url = r('create-student') 31 | 32 | self.client.login( 33 | username='admin_test@admin.com', 34 | password='1234abc' 35 | ) 36 | self.response = self.client.get(self.url) 37 | 38 | def test_get_response_status(self): 39 | self.assertEqual(200, self.response.status_code) 40 | 41 | def test_template_used(self): 42 | self.assertTemplateUsed( 43 | self.response, 44 | 'registration/create_student.html' 45 | ) 46 | 47 | def test_forms_used(self): 48 | userform = self.response.context['userform'] 49 | studentform = self.response.context['studentform'] 50 | self.assertIsInstance(userform, CreateUserForm) 51 | self.assertIsInstance(studentform, StudentForm) 52 | 53 | def test_create_object(self): 54 | response = self.client.post(self.url, self.data) 55 | student = Student.objects.get( 56 | user__email=self.data['email'] 57 | ) 58 | self.assertIsInstance(student, Student) 59 | self.assertRedirects(response, r('home_dashboard')) 60 | 61 | def test_password_validation(self): 62 | self.data['confirm_password'] = '789010abc' 63 | response = self.client.post(self.url, self.data) 64 | userform = response.context['userform'] 65 | self.assertTrue(userform.errors) 66 | self.assertIn( 67 | "Passwords don't match", 68 | userform.errors['confirm_password'] 69 | ) 70 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/classroom/detail_classroom.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %}{% endblock %} 5 | 6 | {% block js %}{% endblock %} 7 | 8 | {% block content %} 9 |
10 | {% for message in messages %} 11 | {{ message }} 12 | {% endfor %} 13 |

{{object.level}} - {{object.number_class}} ({{object.turn}})

14 |

15 | {% if perms.ecweb.view_all_classrooms %} 16 | Update 17 | Delete 18 | {% endif %} 19 |

20 | 21 |
22 | 23 | 24 |

Teachers

25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for student in object.teachers.all %} 33 | 34 | 37 | 40 | 43 | 44 | {% endfor %} 45 | 46 |
First NameLast NameEmail
35 | {{student.user.first_name}} 36 | 38 | {{student.user.last_name}} 39 | 41 | {{student.user}} 42 |
47 | 48 | 49 |

Students

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {% for student in object.students.all %} 58 | 59 | 62 | 65 | 68 | 69 | {% endfor %} 70 | 71 |
First NameLast NameEmail
60 | {{student.user.first_name}} 61 | 63 | {{student.user.last_name}} 64 | 66 | {{student.user}} 67 |
72 |
    73 |

    Classes

    74 | {% if object.class_set.all %} 75 | {% for class in object.class_set.all %} 76 | {{class}} 77 | {% endfor %} 78 | {% else %} 79 | Not found. 80 | {% endif %} 81 |
82 |
83 |
84 | {% endblock %} -------------------------------------------------------------------------------- /ecweb/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth import forms as auth_forms 3 | from .models import BasicUser, Student 4 | from PIL import Image 5 | 6 | 7 | class CreateUserForm(forms.ModelForm): 8 | confirm_password = forms.CharField( 9 | label='Confirm Password', 10 | max_length=128, 11 | widget=forms.PasswordInput() 12 | ) 13 | 14 | class Meta: 15 | model = BasicUser 16 | fields = [ 17 | 'avatar', 'first_name', 18 | 'last_name', 'email', 19 | 'password', 'confirm_password' 20 | ] 21 | widgets = { 22 | 'password': forms.PasswordInput() 23 | } 24 | 25 | def clean_confirm_password(self): 26 | password = self.cleaned_data.get('password') 27 | confirm_password = self.cleaned_data.get('confirm_password') 28 | if password != confirm_password: 29 | raise forms.ValidationError("Passwords don't match") 30 | return super(CreateUserForm, self).clean() 31 | 32 | def save(self, commit=True): 33 | user = super(CreateUserForm, self).save(commit=False) 34 | user.set_password(self.cleaned_data['password']) 35 | if commit: 36 | user.save() 37 | return user 38 | 39 | 40 | class StudentForm(forms.ModelForm): 41 | 42 | class Meta: 43 | model = Student 44 | fields = ['cod', 'type_of_course'] 45 | 46 | 47 | class UpdateUserFormAdmin(CreateUserForm): 48 | password = auth_forms.ReadOnlyPasswordHashField( 49 | label=("Password"), 50 | help_text=( 51 | "Raw passwords are not stored, instead " 52 | "a hash is created from them, and is " 53 | "saved in the database. " 54 | ) 55 | ) 56 | 57 | 58 | class PhotoForm(forms.ModelForm): 59 | x = forms.FloatField(widget=forms.HiddenInput()) 60 | y = forms.FloatField(widget=forms.HiddenInput()) 61 | width = forms.FloatField(widget=forms.HiddenInput()) 62 | height = forms.FloatField(widget=forms.HiddenInput()) 63 | 64 | class Meta: 65 | model = BasicUser 66 | fields = ('avatar', 'x', 'y', 'width', 'height', ) 67 | widgets = { 68 | 'avatar': forms.FileInput(attrs={ 69 | # this is not an actual validation! don't rely on that! 70 | 'accept': 'image/*' 71 | }) 72 | } 73 | 74 | def save(self): 75 | photo = super(PhotoForm, self).save() 76 | 77 | x = self.cleaned_data.get('x') 78 | y = self.cleaned_data.get('y') 79 | w = self.cleaned_data.get('width') 80 | h = self.cleaned_data.get('height') 81 | 82 | image = Image.open(photo.avatar) 83 | cropped_image = image.crop((x, y, w + x, h + y)) 84 | resized_image = cropped_image.resize((200, 200), Image.ANTIALIAS) 85 | resized_image.save(photo.avatar.path) 86 | 87 | return photo 88 | 89 | 90 | class AttendanceForm(forms.Form): 91 | class_id = forms.CharField( 92 | label='Class id', max_length=100, widget=forms.HiddenInput()) 93 | students = forms.MultipleChoiceField( 94 | widget=forms.CheckboxSelectMultiple(), required=False) 95 | -------------------------------------------------------------------------------- /ecweb/static/js/hoverable-collapse.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $(document).on('mouseenter mouseleave', '.sidebar .nav-item', function (ev) { 3 | sidebarMini = $('body').hasClass("sidebar-mini"); 4 | sidebarIconOnly = $('body').hasClass("sidebar-icon-only"); 5 | horizontalMenu = $('body').hasClass("horizontal-menu"); 6 | horizontalMenuTop = $('body').hasClass("horizontal-menu-top"); 7 | boxedLayout = $('body').hasClass("boxed-layout"); 8 | rtlLayout = $('body').hasClass("rtl"); 9 | if (sidebarMini || sidebarIconOnly || horizontalMenu) { 10 | var $menuItem = $(this), 11 | $submenuWrapper = $('> .collapse', $menuItem); 12 | if(ev.type === 'mouseenter') { 13 | $submenuWrapper.addClass('show'); 14 | // grab the menu item's position relative to its positioned parent 15 | var menuItemPos = $menuItem.offset(); 16 | // place the submenu in the correct position relevant to the menu item 17 | if(horizontalMenu) { 18 | if(horizontalMenuTop) { 19 | if(rtlLayout) { 20 | $submenuWrapper.css({ 21 | top: menuItemPos.top+$menuItem.height(), 22 | left: menuItemPos.left, 23 | minWidth: $menuItem.outerWidth() 24 | }); 25 | } 26 | else { 27 | $submenuWrapper.css({ 28 | top: menuItemPos.top+$menuItem.height(), 29 | left: menuItemPos.left - $('.navbar-brand-wrapper').outerWidth(), 30 | minWidth: $menuItem.outerWidth() 31 | }); 32 | } 33 | } 34 | else { 35 | $submenuWrapper.css({ 36 | top: menuItemPos.top+$menuItem.height(), 37 | left: menuItemPos.left, 38 | minWidth: $menuItem.outerWidth() 39 | }); 40 | } 41 | } 42 | else { 43 | if(menuItemPos.top>=$('.sidebar').height()/2){ 44 | $submenuWrapper.css({ 45 | top: menuItemPos.top+$menuItem.height()- $(window).scrollTop()-$submenuWrapper.height() 46 | }); 47 | } 48 | else { 49 | $submenuWrapper.css({ 50 | top: menuItemPos.top- $(window).scrollTop() 51 | }); 52 | } 53 | if(boxedLayout) { 54 | if(rtlLayout) { 55 | $submenuWrapper.css({ 56 | right: $menuItem.outerWidth() + $('.container-scroller').css('padding-right') 57 | }); 58 | } 59 | else { 60 | $submenuWrapper.css({ 61 | left: menuItemPos.left + Math.round($menuItem.outerWidth()-$('.container-scroller').css('padding-left')) 62 | }); 63 | } 64 | } 65 | else { 66 | if(rtlLayout) { 67 | $submenuWrapper.css({ 68 | right: $menuItem.outerWidth() 69 | }); 70 | } 71 | else { 72 | $submenuWrapper.css({ 73 | left: menuItemPos.left + Math.round($menuItem.outerWidth()) 74 | }); 75 | } 76 | } 77 | } 78 | } 79 | else { 80 | $submenuWrapper.removeClass('show'); 81 | } 82 | } 83 | }); 84 | })(jQuery); 85 | -------------------------------------------------------------------------------- /ecweb/static/css/libs/cropper.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Cropper v3.0.0-beta 3 | * https://github.com/fengyuanchen/cropper 4 | * 5 | * Copyright (c) 2017 Fengyuan Chen 6 | * Released under the MIT license 7 | * 8 | * Date: 2017-02-25T07:44:44.656Z 9 | */ 10 | 11 | .cropper-container{font-size:0;line-height:0;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;direction:ltr;-ms-touch-action:none;touch-action:none}.cropper-container img{display:block;min-width:0!important;max-width:none!important;min-height:0!important;max-height:none!important;width:100%;height:100%;image-orientation:0deg}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{position:absolute;top:0;right:0;bottom:0;left:0}.cropper-wrap-box{overflow:hidden}.cropper-drag-box{opacity:0;background-color:#fff}.cropper-modal{opacity:.5;background-color:#000}.cropper-view-box{display:block;overflow:hidden;width:100%;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75)}.cropper-dashed{position:absolute;display:block;opacity:.5;border:0 dashed #eee}.cropper-dashed.dashed-h{top:33.33333%;left:0;width:100%;height:33.33333%;border-top-width:1px;border-bottom-width:1px}.cropper-dashed.dashed-v{top:0;left:33.33333%;width:33.33333%;height:100%;border-right-width:1px;border-left-width:1px}.cropper-center{position:absolute;top:50%;left:50%;display:block;width:0;height:0;opacity:.75}.cropper-center:after,.cropper-center:before{position:absolute;display:block;content:" ";background-color:#eee}.cropper-center:before{top:0;left:-3px;width:7px;height:1px}.cropper-center:after{top:-3px;left:0;width:1px;height:7px}.cropper-face,.cropper-line,.cropper-point{position:absolute;display:block;width:100%;height:100%;opacity:.1}.cropper-face{top:0;left:0;background-color:#fff}.cropper-line{background-color:#39f}.cropper-line.line-e{top:0;right:-3px;width:5px;cursor:e-resize}.cropper-line.line-n{top:-3px;left:0;height:5px;cursor:n-resize}.cropper-line.line-w{top:0;left:-3px;width:5px;cursor:w-resize}.cropper-line.line-s{bottom:-3px;left:0;height:5px;cursor:s-resize}.cropper-point{width:5px;height:5px;opacity:.75;background-color:#39f}.cropper-point.point-e{top:50%;right:-3px;margin-top:-3px;cursor:e-resize}.cropper-point.point-n{top:-3px;left:50%;margin-left:-3px;cursor:n-resize}.cropper-point.point-w{top:50%;left:-3px;margin-top:-3px;cursor:w-resize}.cropper-point.point-s{bottom:-3px;left:50%;margin-left:-3px;cursor:s-resize}.cropper-point.point-ne{top:-3px;right:-3px;cursor:ne-resize}.cropper-point.point-nw{top:-3px;left:-3px;cursor:nw-resize}.cropper-point.point-sw{bottom:-3px;left:-3px;cursor:sw-resize}.cropper-point.point-se{right:-3px;bottom:-3px;width:20px;height:20px;cursor:se-resize;opacity:1}@media (min-width:768px){.cropper-point.point-se{width:15px;height:15px}}@media (min-width:992px){.cropper-point.point-se{width:10px;height:10px}}@media (min-width:1200px){.cropper-point.point-se{width:5px;height:5px;opacity:.75}}.cropper-point.point-se:before{position:absolute;right:-50%;bottom:-50%;display:block;width:200%;height:200%;content:" ";opacity:0;background-color:#39f}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{position:absolute;display:block;width:0;height:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jamesperes/EC.svg?branch=master)](https://travis-ci.org/jamesperes/EC) 2 | 3 | # EC 4 | Project OpenSource for manage students in English College. 5 | 6 | The project is based on the rules of language schools in Ireland and used to show student information during the course. 7 | 8 | **For students:** 9 | - See test rates 10 | - Follow school attendances 11 | - Receive documents, videos and Links from teachers 12 | - Follow nexts events in school 13 | 14 | **For teachers:** 15 | - To plan classes lessons, with videos, links and documents 16 | - To manage the performed tests, giving notes to each student 17 | - To manage the students attendances 18 | - Notify student by emails 19 | - Disclose supplementary events for school 20 | 21 | **For Coordinators:** 22 | - To manage student, teachers and users 23 | - To create class rooms, assigning students, teachers 24 | 25 | ## Entities: 26 | 27 | - Student 28 | - Teacher 29 | - Coordinators 30 | - Class Rooms: 31 | - Number 32 | - Level 33 | - Morning / afternoon 34 | - Many Students 35 | - Many Teachers 36 | - Classes 37 | - Class Room 38 | - Date 39 | - Lessons 40 | - Videos 41 | - Documents 42 | - Students attendances < Class Room 43 | - Tests 44 | - Class room 45 | - Date 46 | - Type 47 | - Grade test for each students < Class room 48 | - Students attendance on the test < Class Room 49 | 50 | ## Running local server 51 | 52 | ### Requeriments 53 | 54 | - Python 3 55 | - Django 2 56 | 57 | ### Install VirtualEnv 58 | 59 | ```sh 60 | $ virtualenv --python=python3 venv 61 | $ source venv/bin/Activate 62 | ``` 63 | 64 | ### For install Requeriments 65 | 66 | ```sh 67 | $ pip install -r req_dev.txt 68 | $ python manage.py migrate 69 | $ python manage.py test 70 | ``` 71 | 72 | 73 | ### For run Server 74 | ```sh 75 | $ python manage.py runserver 76 | ``` 77 | 78 | ### Access to django-admin 79 | 80 | login: admin@admin.com 81 | 82 | password: admin123 83 | 84 | 85 | ### Warm up development's environment by docker compose 86 | ```sh 87 | sudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 88 | ``` 89 | *P.s: S.O other than debian look at [here](https://docs.docker.com/compose/install/)* 90 | 91 | ```sh 92 | cd provision/compose 93 | sudo docker-compose build --no-cache 94 | sudo docker-compose up -d 95 | ``` 96 | 97 | ### Warm up development's environment by docker 98 | ```sh 99 | sudo apt-get remove docker docker-engine docker.io 100 | sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common 101 | curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg | sudo apt-key add - 102 | sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") $(lsb_release -cs) stable" 103 | sudo apt-get update && sudo apt-get install docker-ce 104 | ``` 105 | *P.s: S.O other than debian look at [here](https://docs.docker.com/engine/installation/)* 106 | ```sh 107 | cd provision/docker 108 | sudo docker build -t ec_app -f Dockerfile 109 | sudo docker run -p 8080:8080 -it ec_app 110 | ``` 111 | 112 | ## To debug and logs 113 | ```sh 114 | sudo docker exec -it /bin/bash 115 | sudo docker logs -f --tail=100 116 | ``` 117 | 118 | 119 | 120 | ## Authors 121 | 122 | * **James Peres** - *Initial work* - [jamesperes](https://github.com/jamesperes) 123 | * **Samuel Sampaio** - *Initial work* - [samukasmk](https://github.com/samukasmk) 124 | * **Werberth Vinícius** - *Initial work* - [werberth](https://github.com/werberth) 125 | 126 | See also the list of [contributors](https://github.com/jamesperes/EC/graphs/contributors) who participated in this project. 127 | 128 | ## License 129 | 130 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 131 | -------------------------------------------------------------------------------- /ecweb/static/images/logo_star_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 27 | 29 | 33 | 34 | 35 | 39 | 43 | 45 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ecweb/tests/test_create_user_type_view.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from django.shortcuts import resolve_url as r 3 | from ecweb.models import ( 4 | BasicUser, 5 | Teacher, 6 | Coordinator 7 | ) 8 | from ecweb.forms import CreateUserForm 9 | 10 | 11 | class TestCreateUserTypeView(TestCase): 12 | """ Testing createuser system """ 13 | 14 | def setUp(self): 15 | user = BasicUser.objects.create_superuser( 16 | username='admin', 17 | password='1234abc', 18 | email="admin_test@admin.com" 19 | ) 20 | Coordinator.objects.create(user=user) 21 | self.data_coordinator = { 22 | 'first_name': 'admin', 23 | 'last_name': 'test', 24 | 'email': 'admin_test2@test.com', 25 | 'password': '123456ab', 26 | 'confirm_password': '123456ab' 27 | } 28 | self.data_teacher = { 29 | 'first_name': 'admin2', 30 | 'last_name': 'test2', 31 | 'email': 'admin_test2@test.com', 32 | 'password': '123456ab', 33 | 'confirm_password': '123456ab' 34 | } 35 | self.url_coordinator = r('create-user', 'coordinator') 36 | self.url_teacher = r('create-user', 'teacher') 37 | 38 | self.client.login( 39 | username='admin_test@admin.com', 40 | password='1234abc' 41 | ) 42 | 43 | self.resp_teacher = self.client.get( 44 | self.url_teacher 45 | ) 46 | 47 | self.resp_coordinator = self.client.get( 48 | self.url_coordinator 49 | ) 50 | 51 | def test_response_status_with_type_coordinator(self): 52 | self.assertEqual(200, self.resp_coordinator.status_code) 53 | 54 | def test_response_status_with_type_teacher(self): 55 | self.assertEqual(200, self.resp_teacher.status_code) 56 | 57 | def test_template_used(self): 58 | self.assertTemplateUsed( 59 | self.resp_coordinator, 'registration/create_user.html') 60 | self.assertTemplateUsed( 61 | self.resp_teacher, 'registration/create_user.html') 62 | 63 | def test_forms_used(self): 64 | coordinatorform = self.resp_coordinator.context['form'] 65 | teacherform = self.resp_teacher.context['form'] 66 | self.assertIsInstance(coordinatorform, CreateUserForm) 67 | self.assertIsInstance(teacherform, CreateUserForm) 68 | 69 | def test_create_object_type_coordinator(self): 70 | self._test_create_object( 71 | data=self.data_coordinator, 72 | url=self.url_coordinator, 73 | model=Coordinator, 74 | ) 75 | 76 | def test_create_object_type_teacher(self): 77 | self._test_create_object( 78 | data=self.data_teacher, 79 | url=self.url_teacher, 80 | model=Teacher, 81 | ) 82 | 83 | def test_form_password_validation_on_teacher_post(self): 84 | self._test_password_validation( 85 | data=self.data_teacher, 86 | url=self.url_teacher 87 | ) 88 | 89 | def test_form_password_validation_on_coordinator_post(self): 90 | self._test_password_validation( 91 | data=self.data_coordinator, 92 | url=self.url_coordinator 93 | ) 94 | 95 | def _test_password_validation(self, data, url): 96 | data['confirm_password'] = '789010abc' 97 | response = self.client.post(url, data) 98 | form = response.context['form'] 99 | self.assertTrue(form.errors) 100 | self.assertIn("Passwords don't match", form.errors['confirm_password']) 101 | 102 | def _test_create_object(self, data, model, url): 103 | response = self.client.post(url, data) 104 | instance = model.objects.get( 105 | user__email=data['email'] 106 | ) 107 | self.assertIsInstance(instance, model) 108 | self.assertRedirects(response, r('home_dashboard')) 109 | -------------------------------------------------------------------------------- /EC/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for EC project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/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 | 18 | MEDIA_ROOT = BASE_DIR + '/media' 19 | 20 | MEDIA_URL = '/media/' 21 | 22 | # Quick-start development settings - unsuitable for production 23 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 24 | 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = 'yt(%4d^o0^ys9_^v9j45qqu5cb)=c#i0_y$w&t+so*^km@#e_c' 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = True 30 | 31 | ALLOWED_HOSTS = [] 32 | 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.admin', 38 | 'django.contrib.auth', 39 | 'django.contrib.contenttypes', 40 | 'django.contrib.sessions', 41 | 'django.contrib.messages', 42 | 'django.contrib.staticfiles', 43 | 'ecweb' 44 | ] 45 | 46 | MIDDLEWARE = [ 47 | 'django.middleware.security.SecurityMiddleware', 48 | 'django.contrib.sessions.middleware.SessionMiddleware', 49 | 'django.middleware.common.CommonMiddleware', 50 | 'django.middleware.csrf.CsrfViewMiddleware', 51 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 52 | 'django.contrib.messages.middleware.MessageMiddleware', 53 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 54 | ] 55 | 56 | ROOT_URLCONF = 'EC.urls' 57 | 58 | TEMPLATES = [ 59 | { 60 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 61 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 62 | 'APP_DIRS': True, 63 | 'OPTIONS': { 64 | 'context_processors': [ 65 | 'django.template.context_processors.debug', 66 | 'django.template.context_processors.request', 67 | 'django.contrib.auth.context_processors.auth', 68 | 'django.contrib.messages.context_processors.messages', 69 | ], 70 | }, 71 | }, 72 | ] 73 | 74 | WSGI_APPLICATION = 'EC.wsgi.application' 75 | 76 | 77 | # Database 78 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 79 | 80 | DATABASES = { 81 | 'default': { 82 | 'ENGINE': 'django.db.backends.sqlite3', 83 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 84 | } 85 | } 86 | 87 | 88 | # Password validation 89 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 90 | 91 | AUTH_PASSWORD_VALIDATORS = [ 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 103 | }, 104 | ] 105 | 106 | 107 | 108 | # Internationalization 109 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 110 | 111 | LANGUAGE_CODE = 'en-us' 112 | 113 | TIME_ZONE = 'UTC' 114 | 115 | USE_I18N = True 116 | 117 | USE_L10N = True 118 | 119 | USE_TZ = True 120 | 121 | 122 | # Static files (CSS, JavaScript, Images) 123 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 124 | 125 | STATIC_URL = '/static/' 126 | 127 | LOGIN_REDIRECT_URL = '/board' 128 | 129 | AUTH_USER_MODEL = 'ecweb.BasicUser' 130 | 131 | if os.environ.get("DOCKER_DEVELOPMENT"): 132 | try: 133 | from EC.docker_settings import * 134 | except ImportError: 135 | pass 136 | 137 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' -------------------------------------------------------------------------------- /ecweb/static/images/logo_star.svg: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 26 | 28 | 32 | 34 | 35 | 39 | 43 | 46 | 49 | 50 | -------------------------------------------------------------------------------- /ecweb/static/css/libs/perfect-scrollbar.min.css: -------------------------------------------------------------------------------- 1 | /* perfect-scrollbar v0.7.1 */ 2 | .ps{-ms-touch-action:auto;touch-action:auto;overflow:hidden !important;-ms-overflow-style:none}@supports (-ms-overflow-style: none){.ps{overflow:auto !important}}@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none){.ps{overflow:auto !important}}.ps.ps--active-x>.ps__scrollbar-x-rail,.ps.ps--active-y>.ps__scrollbar-y-rail{display:block;background-color:transparent}.ps.ps--in-scrolling.ps--x>.ps__scrollbar-x-rail{background-color:#eee;opacity:.9}.ps.ps--in-scrolling.ps--x>.ps__scrollbar-x-rail>.ps__scrollbar-x{background-color:#999;height:11px}.ps.ps--in-scrolling.ps--y>.ps__scrollbar-y-rail{background-color:#eee;opacity:.9}.ps.ps--in-scrolling.ps--y>.ps__scrollbar-y-rail>.ps__scrollbar-y{background-color:#999;width:11px}.ps>.ps__scrollbar-x-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;bottom:0px;height:15px}.ps>.ps__scrollbar-x-rail>.ps__scrollbar-x{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;bottom:2px;height:6px}.ps>.ps__scrollbar-x-rail:hover>.ps__scrollbar-x,.ps>.ps__scrollbar-x-rail:active>.ps__scrollbar-x{height:11px}.ps>.ps__scrollbar-y-rail{display:none;position:absolute;opacity:0;-webkit-transition:background-color .2s linear, opacity .2s linear;-moz-transition:background-color .2s linear, opacity .2s linear;-o-transition:background-color .2s linear, opacity .2s linear;transition:background-color .2s linear, opacity .2s linear;right:0;width:15px}.ps>.ps__scrollbar-y-rail>.ps__scrollbar-y{position:absolute;background-color:#aaa;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, -webkit-border-radius .2s ease-in-out;-moz-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;-o-transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out;transition:background-color .2s linear, height .2s linear, width .2s ease-in-out, border-radius .2s ease-in-out, -webkit-border-radius .2s ease-in-out, -moz-border-radius .2s ease-in-out;right:2px;width:6px}.ps>.ps__scrollbar-y-rail:hover>.ps__scrollbar-y,.ps>.ps__scrollbar-y-rail:active>.ps__scrollbar-y{width:11px}.ps:hover.ps--in-scrolling.ps--x>.ps__scrollbar-x-rail{background-color:#eee;opacity:.9}.ps:hover.ps--in-scrolling.ps--x>.ps__scrollbar-x-rail>.ps__scrollbar-x{background-color:#999;height:11px}.ps:hover.ps--in-scrolling.ps--y>.ps__scrollbar-y-rail{background-color:#eee;opacity:.9}.ps:hover.ps--in-scrolling.ps--y>.ps__scrollbar-y-rail>.ps__scrollbar-y{background-color:#999;width:11px}.ps:hover>.ps__scrollbar-x-rail,.ps:hover>.ps__scrollbar-y-rail{opacity:.6}.ps:hover>.ps__scrollbar-x-rail:hover{background-color:#eee;opacity:.9}.ps:hover>.ps__scrollbar-x-rail:hover>.ps__scrollbar-x{background-color:#999}.ps:hover>.ps__scrollbar-y-rail:hover{background-color:#eee;opacity:.9}.ps:hover>.ps__scrollbar-y-rail:hover>.ps__scrollbar-y{background-color:#999} 3 | -------------------------------------------------------------------------------- /ecweb/static/js/chart.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | /* ChartJS 3 | * ------- 4 | * Data and config for chartjs 5 | */ 6 | var data = { 7 | labels: ["Red", "teste", "Yellow", "Green", "Purple", "Orange"], 8 | datasets: [{ 9 | label: '# of Votes', 10 | data: [12, 21, 3, 5, 2, 3], 11 | backgroundColor: [ 12 | 'rgba(255, 99, 132, 0.2)', 13 | 'rgba(54, 162, 235, 0.2)', 14 | 'rgba(255, 206, 86, 0.2)', 15 | 'rgba(75, 192, 192, 0.2)', 16 | 'rgba(153, 102, 255, 0.2)', 17 | 'rgba(255, 159, 64, 0.2)' 18 | ], 19 | borderColor: [ 20 | 'rgba(255,99,132,1)', 21 | 'rgba(54, 162, 235, 1)', 22 | 'rgba(255, 206, 86, 1)', 23 | 'rgba(75, 192, 192, 1)', 24 | 'rgba(153, 102, 255, 1)', 25 | 'rgba(255, 159, 64, 1)' 26 | ], 27 | borderWidth: 1 28 | }] 29 | }; 30 | var options = { 31 | scales: { 32 | yAxes: [{ 33 | ticks: { 34 | beginAtZero:true 35 | } 36 | }] 37 | } 38 | }; 39 | var doughnutData ={ 40 | datasets: [{ 41 | data: [30, 40, 30], 42 | backgroundColor: ['#d9534f','#36a2eb','#5cb85c'] 43 | }], 44 | 45 | // These labels appear in the legend and in the tooltips when hovering different arcs 46 | labels: [ 47 | 'Red', 48 | 'Blue', 49 | 'Green' 50 | ] 51 | }; 52 | var doughnutOptions = { 53 | responsive: true, 54 | animation: { 55 | animateScale: true, 56 | animateRotate: true 57 | } 58 | }; 59 | 60 | var areaData = { 61 | labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"], 62 | datasets: [{ 63 | label: '# of Votes', 64 | data: [12, 19, 3, 5, 2, 3], 65 | backgroundColor: [ 66 | 'rgba(255, 99, 132, 0.2)', 67 | 'rgba(54, 162, 235, 0.2)', 68 | 'rgba(255, 206, 86, 0.2)', 69 | 'rgba(75, 192, 192, 0.2)', 70 | 'rgba(153, 102, 255, 0.2)', 71 | 'rgba(255, 159, 64, 0.2)' 72 | ], 73 | borderColor: [ 74 | 'rgba(255,99,132,1)', 75 | 'rgba(54, 162, 235, 1)', 76 | 'rgba(255, 206, 86, 1)', 77 | 'rgba(75, 192, 192, 1)', 78 | 'rgba(153, 102, 255, 1)', 79 | 'rgba(255, 159, 64, 1)' 80 | ], 81 | borderWidth: 1, 82 | fill: 'origin', // 0: fill to 'origin' 83 | fill: '+2', // 1: fill to dataset 3 84 | fill: 1, // 2: fill to dataset 1 85 | fill: false, // 3: no fill 86 | fill: '-2' // 4: fill to dataset 2 87 | }] 88 | }; 89 | 90 | var areaOptions = { 91 | plugins: { 92 | filler: { 93 | propagate: true 94 | } 95 | } 96 | } 97 | // Get context with jQuery - using jQuery's .get() method. 98 | if($("#barChart").length) { 99 | var barChartCanvas = $("#barChart").get(0).getContext("2d"); 100 | // This will get the first returned node in the jQuery collection. 101 | var barChart = new Chart(barChartCanvas, { 102 | type: 'bar', 103 | data: data, 104 | options: options 105 | }); 106 | } 107 | 108 | if($("#lineChart").length) { 109 | var lineChartCanvas = $("#lineChart").get(0).getContext("2d"); 110 | var lineChart = new Chart(lineChartCanvas, { 111 | type: 'line', 112 | data: data, 113 | options: options 114 | }); 115 | } 116 | 117 | if($("#doughnutChart").length) { 118 | var doughnutChartCanvas = $("#doughnutChart").get(0).getContext("2d"); 119 | var doughnutChart = new Chart(doughnutChartCanvas, { 120 | type: 'doughnut', 121 | data: doughnutData, 122 | options: doughnutOptions 123 | }); 124 | } 125 | 126 | if($("#areaChart").length) { 127 | var areaChartCanvas = $("#areaChart").get(0).getContext("2d"); 128 | var areaChart = new Chart(areaChartCanvas, { 129 | type:'line', 130 | data: areaData, 131 | options: areaOptions 132 | }); 133 | } 134 | 135 | }); 136 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/student.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | 6 | 7 | 8 | {% endblock %} 9 | 10 | 11 | 12 | {% block js %} 13 | 14 | 15 | 71 | 72 | 100 | 101 | 102 | {% endblock %} 103 | 104 | {% block content %} 105 |
106 | 107 |

Student Dashboard

108 | 109 |
110 | 164 | 165 |
166 |
167 |
168 | {% endblock %} 169 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/baseadmin.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | EC Admin 9 | 10 | 11 | 12 | 13 | {% block head %}{% endblock %} 14 | 15 | 16 |
17 | 18 | 41 | 42 |
43 |
44 | 96 | 97 | 98 | {% block content %}{% endblock %} 99 | 100 |
101 |
102 | 103 | EC Admin © 2017 104 | 105 |
106 |
107 |
108 |
109 |
110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | {% block js %}{% endblock %} 120 | 121 | 122 | -------------------------------------------------------------------------------- /ecweb/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils.translation import ugettext_lazy as _ 3 | from .utils.li import ( 4 | level_choices, 5 | type_list, 6 | test_choices, 7 | classroom_turns_choices 8 | ) 9 | from django.utils.text import slugify 10 | from django.urls import reverse 11 | from django.contrib.auth.models import Permission 12 | 13 | 14 | from django.contrib.auth.models import AbstractUser 15 | 16 | import geocoder 17 | 18 | 19 | class BasicUser(AbstractUser): 20 | email = models.EmailField( 21 | _('email address'), null=False, blank=False, unique=True) 22 | 23 | username = models.CharField( 24 | _('username'), 25 | max_length=150, 26 | blank=True, 27 | null=True) 28 | 29 | avatar = models.ImageField( 30 | upload_to='avatars/profiles', 31 | default='avatars/user_default.png', 32 | blank=True) 33 | 34 | USERNAME_FIELD = 'email' 35 | REQUIRED_FIELDS = [] 36 | 37 | 38 | class Student(models.Model): 39 | user = models.OneToOneField(BasicUser, on_delete=models.CASCADE) 40 | cod = models.IntegerField(blank=False, null=False) 41 | type_of_course = models.CharField( 42 | max_length=30, choices=type_list, blank=False, null=False) 43 | 44 | def __str__(self): 45 | return self.user.first_name 46 | 47 | 48 | class Teacher(models.Model): 49 | user = models.OneToOneField(BasicUser, on_delete=models.CASCADE) 50 | 51 | def save(self, *args, **kwargs): 52 | self.user.is_staff = True 53 | self.user.save() 54 | 55 | super(Teacher, self).save(*args, **kwargs) 56 | 57 | def __str__(self): 58 | return self.user.first_name 59 | 60 | 61 | class Coordinator(models.Model): 62 | user = models.OneToOneField(BasicUser, on_delete=models.CASCADE) 63 | 64 | class Meta: 65 | permissions = ( 66 | ("view_all_classrooms", "Can view all classrooms"), 67 | ) 68 | 69 | def save(self, *args, **kwargs): 70 | permission = Permission.objects.get(codename='view_all_classrooms') 71 | 72 | self.user.user_permissions.add(permission) 73 | self.user.is_staff = True 74 | self.user.save() 75 | 76 | super(Coordinator, self).save(*args, **kwargs) 77 | 78 | def __str__(self): 79 | return self.user.first_name 80 | 81 | 82 | class ClassRoom(models.Model): 83 | number_class = models.PositiveIntegerField(blank=True) 84 | level = models.CharField(max_length=30, choices=level_choices, blank=True) 85 | students = models.ManyToManyField(Student) 86 | teachers = models.ManyToManyField(Teacher) 87 | turn = models.CharField(max_length=50, choices=classroom_turns_choices) 88 | slug = models.SlugField(null=True, blank=True) 89 | is_active = models.BooleanField(default=True) 90 | 91 | def save(self, *args, **kwargs): 92 | self.slug = slugify(self.__str__()) 93 | super(ClassRoom, self).save(*args, **kwargs) 94 | 95 | def get_absolute_url(self): 96 | return reverse('classroom_detail_view', kwargs={'slug': self.slug}) 97 | 98 | def __str__(self): 99 | return '{}: {} - {}'.format(self.level, self.turn, self.number_class) 100 | 101 | 102 | class Youtube(models.Model): 103 | description = models.CharField(max_length=50, blank=True) 104 | link = models.CharField(max_length=255, blank=True) 105 | 106 | def __str__(self): 107 | return self.description 108 | 109 | 110 | class PdfFile(models.Model): 111 | description = models.CharField(max_length=50, blank=True) 112 | file = models.FileField(upload_to="media/", blank=True) 113 | 114 | def __str__(self): 115 | return self.description 116 | 117 | 118 | class Class(models.Model): 119 | classroom = models.ForeignKey(ClassRoom, on_delete=models.CASCADE) 120 | date = models.DateField() 121 | lesson = models.TextField() 122 | videos = models.ManyToManyField(Youtube, blank=True) 123 | files = models.ManyToManyField(PdfFile, blank=True) 124 | attendances = models.ManyToManyField(Student, blank=True) 125 | 126 | def __str__(self): 127 | return "{}: {}".format(self.date, self.lesson[:30]) 128 | 129 | 130 | class Test(models.Model): 131 | classroom = models.ForeignKey(ClassRoom, on_delete=models.CASCADE) 132 | date = models.DateField() 133 | type = models.CharField(max_length=50, choices=test_choices) 134 | 135 | def __str__(self): 136 | return "{}: {}".format(self.date, self.lesson[:30]) 137 | 138 | 139 | class TestGrade(models.Model): 140 | test_event = models.ForeignKey(Test, on_delete=models.CASCADE) 141 | student = models.ForeignKey(Student, on_delete=models.CASCADE) 142 | grade = models.FloatField() 143 | 144 | 145 | class Event(models.Model): 146 | description = models.TextField(max_length=500) 147 | title = models.CharField(max_length=50) 148 | start_event = models.DateTimeField() 149 | end_event = models.DateTimeField(blank=True) 150 | address = models.CharField(max_length=250) 151 | neighborhood = models.CharField(max_length=250) 152 | city = models.CharField(max_length=250) 153 | local_lat = models.CharField(null=True, blank=True, max_length=250) 154 | local_long = models.CharField(null=True, blank=True, max_length=250) 155 | limit = models.IntegerField(blank=True) 156 | teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE, blank=True) 157 | 158 | def __str__(self): 159 | return self.title 160 | 161 | def geocoder(self): 162 | end_str = '{},{},{}'.format(self.address, self.neighborhood, self.city) 163 | g = geocoder.google(end_str) 164 | return [g.lat, g.lng] 165 | 166 | def update_geocode(self): 167 | try: 168 | self.local_lat, self.local_long = self.geocoder() 169 | except: 170 | self.local_lat, self.local_long = [None, None] 171 | 172 | def save(self, *args, **kwargs): 173 | self.update_geocode() 174 | super(Event, self).save(*args, **kwargs) 175 | -------------------------------------------------------------------------------- /ecweb/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2017-12-24 05:13 2 | 3 | from django.conf import settings 4 | import django.contrib.auth.models 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import django.utils.timezone 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('auth', '0009_alter_user_last_name_max_length'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='BasicUser', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('password', models.CharField(max_length=128, verbose_name='password')), 24 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 25 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 26 | ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 29 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 30 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 31 | ('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')), 32 | ('username', models.CharField(blank=True, max_length=150, null=True, verbose_name='username')), 33 | ('avatar', models.ImageField(blank=True, default='avatars/user_default.png', upload_to='avatars/profiles')), 34 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 35 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 36 | ], 37 | options={ 38 | 'verbose_name': 'user', 39 | 'verbose_name_plural': 'users', 40 | 'abstract': False, 41 | }, 42 | managers=[ 43 | ('objects', django.contrib.auth.models.UserManager()), 44 | ], 45 | ), 46 | migrations.CreateModel( 47 | name='Class', 48 | fields=[ 49 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 50 | ('date', models.DateField()), 51 | ('lesson', models.TextField()), 52 | ], 53 | ), 54 | migrations.CreateModel( 55 | name='ClassRoom', 56 | fields=[ 57 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 58 | ('number_class', models.IntegerField(blank=True)), 59 | ('level', models.CharField(blank=True, choices=[('Beginner', 'Beginner'), ('Elementary', 'Elementary')], max_length=30)), 60 | ], 61 | ), 62 | migrations.CreateModel( 63 | name='Coordinator', 64 | fields=[ 65 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 66 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 67 | ], 68 | ), 69 | migrations.CreateModel( 70 | name='PdfFile', 71 | fields=[ 72 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 73 | ('description', models.CharField(blank=True, max_length=50)), 74 | ('file', models.FileField(blank=True, upload_to='media/')), 75 | ], 76 | ), 77 | migrations.CreateModel( 78 | name='Student', 79 | fields=[ 80 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 81 | ('cod', models.IntegerField()), 82 | ('type_of_course', models.CharField(choices=[('1-month', '1-month'), ('6-month', '6-month')], max_length=30)), 83 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 84 | ], 85 | ), 86 | migrations.CreateModel( 87 | name='Teacher', 88 | fields=[ 89 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 90 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 91 | ], 92 | ), 93 | migrations.CreateModel( 94 | name='Test', 95 | fields=[ 96 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 97 | ('date', models.DateField()), 98 | ('type', models.CharField(choices=[('listening', 'Listening'), ('reading', 'Reading')], max_length=50)), 99 | ('classroom', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ecweb.ClassRoom')), 100 | ], 101 | ), 102 | migrations.CreateModel( 103 | name='TestGrade', 104 | fields=[ 105 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 106 | ('grade', models.FloatField()), 107 | ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ecweb.Student')), 108 | ('test_event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ecweb.Test')), 109 | ], 110 | ), 111 | migrations.CreateModel( 112 | name='Youtube', 113 | fields=[ 114 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 115 | ('description', models.CharField(blank=True, max_length=50)), 116 | ('link', models.CharField(blank=True, max_length=255)), 117 | ], 118 | ), 119 | migrations.AddField( 120 | model_name='classroom', 121 | name='students', 122 | field=models.ManyToManyField(to='ecweb.Student'), 123 | ), 124 | migrations.AddField( 125 | model_name='classroom', 126 | name='teachers', 127 | field=models.ManyToManyField(to='ecweb.Teacher'), 128 | ), 129 | migrations.AddField( 130 | model_name='class', 131 | name='attendances', 132 | field=models.ManyToManyField(blank=True, to='ecweb.Student'), 133 | ), 134 | migrations.AddField( 135 | model_name='class', 136 | name='classroom', 137 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='ecweb.ClassRoom'), 138 | ), 139 | migrations.AddField( 140 | model_name='class', 141 | name='files', 142 | field=models.ManyToManyField(blank=True, to='ecweb.PdfFile'), 143 | ), 144 | migrations.AddField( 145 | model_name='class', 146 | name='videos', 147 | field=models.ManyToManyField(blank=True, to='ecweb.Youtube'), 148 | ), 149 | ] 150 | -------------------------------------------------------------------------------- /ecweb/templates/ecweb/student-dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'ecweb/baseadmin.html' %} 2 | {% load staticfiles %} 3 | 4 | {% block head %} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block js %} 10 | 11 | 45 | {% endblock %} 46 | 47 | {% block content %} 48 |
49 | 50 |

Student Dashboard

51 |
52 |
53 |
54 |
55 |

{{ days_cont_int }} Days

56 |

Days studies

57 |
58 |
75%
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |

10 Days

68 |

Attendance

69 |
70 |
40%
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |

Next Event

80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 |
Conversetion Class20/12 - 17hRoom 02
89 |
90 |
91 |
92 | 93 |
94 |
95 |
96 |
97 |
98 |
Progress Grade
99 | 100 |
101 |
102 |
103 |
104 |
105 |
106 |
Last Uploads
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 124 | 125 | 127 | 128 | 129 | 130 | 131 | 132 | 134 | 135 | 136 | 137 | 138 | 139 | 141 | 142 | 143 | 144 | 145 | 146 | 148 | 149 | 150 | 151 | 152 | 153 |
TeacherNameItem
profileLarryVerb ListPDF
profileJonhnews Donald TrumpLink
profileJonhClip - RHCPVideo
profileLarryAcerSuccess
profileLarryAcerFailed
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | 162 | Star Admin © 2017 163 | 164 |
165 |
166 | 167 | 168 | 169 | 170 | {% endblock %} 171 | 172 | {% block user_title %} 173 |

Student

174 | {% endblock %} 175 | -------------------------------------------------------------------------------- /ecweb/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.http import HttpResponseRedirect, Http404 3 | from django.urls import reverse as r 4 | from django.contrib.auth.decorators import login_required 5 | from datetime import date 6 | from django.urls import reverse_lazy 7 | from django.contrib.auth.mixins import PermissionRequiredMixin 8 | from django.contrib.auth.mixins import LoginRequiredMixin 9 | from django.contrib.auth import logout 10 | from django.contrib import messages 11 | from django.views.generic import CreateView 12 | from django.views.generic.list import ListView 13 | from django.views.generic.detail import DetailView 14 | from django.views.generic.edit import UpdateView, DeleteView 15 | from django.contrib import messages 16 | from django.contrib.auth import update_session_auth_hash 17 | from django.contrib.auth.forms import PasswordChangeForm 18 | 19 | from django.contrib.auth import logout 20 | from .forms import PhotoForm, AttendanceForm, CreateUserForm, StudentForm 21 | from .models import ClassRoom, Teacher, Student, Class, BasicUser, Coordinator,Event 22 | 23 | 24 | @login_required 25 | def create_user_type_view(request, user_type): 26 | template_name = 'registration/create_user.html' 27 | types = ('coordinator', 'teacher') 28 | current_user = request.user 29 | if user_type not in types: 30 | raise Http404 31 | if request.method == 'POST': 32 | form = CreateUserForm(request.POST) 33 | if form.is_valid(): 34 | user = form.save() 35 | if user_type == 'coordinator': 36 | Coordinator.objects.create(user=user) 37 | elif user_type == 'teacher': 38 | Teacher.objects.create(user=user) 39 | return redirect(r('home_dashboard')) 40 | else: 41 | form = CreateUserForm() 42 | context = {'form': form, 43 | 'current_user': current_user} 44 | return render(request, template_name, context) 45 | 46 | 47 | @login_required 48 | def create_student_view(request): 49 | template_name = 'registration/create_student.html' 50 | current_user = request.user 51 | if request.method == 'POST': 52 | userform = CreateUserForm(request.POST) 53 | studentform = StudentForm(request.POST) 54 | if userform.is_valid() and studentform.is_valid(): 55 | user = userform.save() 56 | student = studentform.save(commit=False) 57 | student.user = user 58 | student.save() 59 | return redirect(r('home_dashboard')) 60 | else: 61 | userform = CreateUserForm() 62 | studentform = StudentForm() 63 | context = { 64 | 'userform': userform, 65 | 'studentform': studentform, 66 | 'current_user': current_user 67 | } 68 | return render(request, template_name, context) 69 | 70 | 71 | @login_required 72 | def home_dashboard(request): 73 | current_user = request.user 74 | user = Coordinator.objects.filter(user__id=current_user.id) 75 | 76 | if user: 77 | return render(request, 'ecweb/coordinator-dashboard.html', 78 | {'coordinator': user.first(), 79 | 'current_user': current_user}) 80 | 81 | user = Teacher.objects.filter(user__id=current_user.id) 82 | if user: 83 | return render(request, 'ecweb/teacher-dashboard.html', 84 | {'teacher': user.first(), 85 | 'current_user': current_user}) 86 | 87 | user = Student.objects.filter(user__id=current_user.id) 88 | if user: 89 | date_start = current_user.date_joined.date() 90 | days_con = date.today() - date_start 91 | days_cont_int = int(days_con.days) 92 | if user[0].type_of_course == "1-month": 93 | count_day = 30 - days_cont_int 94 | percent = int(-100.0 * (count_day / 30)) 95 | 96 | else: 97 | count_day = 30 * 6 - days_cont_int 98 | percent = int(100.0 * (count_day / (30 * 6))) 99 | 100 | return render(request, 'ecweb/student-dashboard.html', 101 | {'student': user.first(), 102 | 'current_user': current_user, 103 | 'days_cont_int': days_cont_int}) 104 | 105 | raise Http404 106 | 107 | 108 | @login_required 109 | def user_detail(request): 110 | current_user = request.user 111 | insta = get_object_or_404(BasicUser, pk=int(current_user.id)) 112 | 113 | if request.method == 'POST': 114 | if "change_password" in request.POST: 115 | 116 | form = PasswordChangeForm(request.user, request.POST) 117 | if form.is_valid(): 118 | user = form.save() 119 | update_session_auth_hash(request, user) # Important! 120 | messages.success( 121 | request, 'Your password was successfully updated!') 122 | return redirect('user_detail') 123 | else: 124 | form = PasswordChangeForm(request.user) 125 | return render(request, 'ecweb/student.html', { 126 | 'form': form 127 | }) 128 | 129 | else: 130 | 131 | form = PhotoForm(request.POST, request.FILES, instance=insta) 132 | if form.is_valid(): 133 | profil = form.save() 134 | profil.user = current_user 135 | profil.save() 136 | 137 | return redirect('user_detail') 138 | else: 139 | form = PhotoForm() 140 | return render(request, 'ecweb/student.html', 141 | {'current_user': current_user, 'form': form}) 142 | 143 | 144 | @login_required 145 | def change_password(request): 146 | if request.method == 'POST': 147 | form = PasswordChangeForm(request.user, request.POST) 148 | if form.is_valid(): 149 | user = form.save() 150 | update_session_auth_hash(request, user) # Important! 151 | messages.success( 152 | request, 'Your password was successfully updated!') 153 | return redirect('change_password') 154 | else: 155 | messages.error(request, 'Please correct the error below.') 156 | else: 157 | form = PasswordChangeForm(request.user) 158 | return render(request, 'registration/change-password.html', { 159 | 'form': form 160 | }) 161 | 162 | 163 | def logout_view(request): 164 | logout(request) 165 | return render(request, 'registration/logout.html') 166 | 167 | 168 | @login_required 169 | def calendar_view(request): 170 | events = Calendar.objects.all() 171 | return render(request, 'ecweb/calendar.html', {'events': events}) 172 | 173 | 174 | class ClassRoomListView(LoginRequiredMixin, ListView): 175 | 176 | model = ClassRoom 177 | template_name = 'ecweb/classroom/classroom.html' 178 | 179 | def get_queryset(self): 180 | current_user = self.request.user 181 | 182 | queryset = super(ClassRoomListView, self).get_queryset() 183 | 184 | if current_user.is_staff: 185 | teacher = Teacher.objects.filter(user=current_user.id) 186 | 187 | if teacher.exists(): 188 | queryset = ClassRoom.objects.filter( 189 | teachers=teacher.first().id, 190 | is_active=True 191 | ) 192 | 193 | else: 194 | queryset = ClassRoom.objects.filter(is_active=True) 195 | 196 | else: 197 | student = Student.objects.get(user=current_user.id) 198 | queryset = ClassRoom.objects.filter( 199 | students=student.id, is_active=True) 200 | 201 | return queryset 202 | 203 | 204 | class ClassRoomDetailView(LoginRequiredMixin, DetailView): 205 | model = ClassRoom 206 | template_name = 'ecweb/classroom/detail_classroom.html' 207 | 208 | def dispatch(self, request, *args, **kwargs): 209 | classroom = self.get_object() 210 | user = request.user 211 | 212 | is_coordinator = Coordinator.objects.filter(user=user).exists() 213 | student_in_classroom = classroom.students.all().filter(user=user).exists() 214 | teacher_in_classroom = classroom.teachers.all().filter(user=user).exists() 215 | 216 | if not (student_in_classroom or teacher_in_classroom or is_coordinator): 217 | return redirect('classroom_view') 218 | 219 | if not classroom.is_active: 220 | return redirect('classroom_view') 221 | 222 | return super(ClassRoomDetailView, self).dispatch(request, *args, **kwargs) 223 | 224 | 225 | class ClassRoomCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): 226 | model = ClassRoom 227 | template_name = 'ecweb/classroom/create_classroom.html' 228 | success_url = reverse_lazy('classroom_view') 229 | permission_required = 'ecweb.view_all_classrooms' 230 | fields = ( 231 | 'number_class', 232 | 'level', 233 | 'students', 234 | 'teachers', 235 | 'turn' 236 | ) 237 | 238 | def form_valid(self, form): 239 | 240 | self.object = form.save(commit=False) 241 | 242 | classroom_exists = ClassRoom.objects.filter( 243 | number_class=self.object.number_class, 244 | level=self.object.level, 245 | turn=self.object.turn 246 | ).exists() 247 | 248 | if classroom_exists: 249 | messages.error( 250 | self.request, 251 | 'This classroom already exists.' 252 | ) 253 | return super(ClassRoomCreateView, self).form_invalid(form) 254 | 255 | return super(ClassRoomCreateView, self).form_valid(form) 256 | 257 | 258 | class ClassRoomUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): 259 | model = ClassRoom 260 | template_name = 'ecweb/classroom/update_classroom.html' 261 | permission_required = 'ecweb.view_all_classrooms' 262 | fields = ( 263 | 'number_class', 264 | 'level', 265 | 'students', 266 | 'teachers', 267 | 'turn' 268 | ) 269 | 270 | def form_valid(self, form): 271 | 272 | self.object = form.save(commit=False) 273 | form_changed = form.has_changed() 274 | 275 | if form_changed: 276 | classroom_exists = ClassRoom.objects.filter( 277 | number_class=self.object.number_class, 278 | level=self.object.level, 279 | turn=self.object.turn 280 | ).exists() 281 | 282 | if classroom_exists: 283 | messages.error( 284 | self.request, 285 | 'This classroom already exists.' 286 | ) 287 | return super(ClassRoomUpdateView, self).form_invalid(form) 288 | else: 289 | messages.success( 290 | self.request, 291 | 'Classroom successfully updated' 292 | ) 293 | 294 | return super(ClassRoomUpdateView, self).form_valid(form) 295 | 296 | messages.info( 297 | self.request, 298 | 'The classroom does changed.' 299 | ) 300 | return super(ClassRoomUpdateView, self).form_valid(form) 301 | 302 | 303 | class ClassRoomDeactivateView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): 304 | model = ClassRoom 305 | template_name = 'ecweb/classroom/classroom_confirm_delete.html' 306 | success_url = reverse_lazy('classroom_view') 307 | permission_required = 'ecweb.view_all_classrooms' 308 | 309 | def delete(self, request, *args, **kwargs): 310 | self.object = self.get_object() 311 | success_url = self.get_success_url() 312 | self.object.is_active = False 313 | self.object.save() 314 | 315 | return HttpResponseRedirect(success_url) 316 | 317 | 318 | @login_required 319 | def list_classes_view(request, class_room_id): 320 | 321 | current_user = request.user 322 | user = Coordinator.objects.filter(user__id=current_user.id) 323 | if user: 324 | all_classes = Class.objects.filter(classroom=class_room_id) 325 | 326 | user = Teacher.objects.filter(user__id=current_user.id) 327 | if user: 328 | teacher = Teacher.objects.get(user=current_user.id) 329 | all_classes = Class.objects.filter(classroom__teachers=teacher.id) 330 | 331 | user = Student.objects.filter(user__id=current_user.id) 332 | if user: 333 | student = Student.objects.get(user=current_user.id) 334 | all_classes = Class.objects.filter(classroom__students=student.id) 335 | 336 | context = { 337 | 'all_classes': all_classes, 338 | 'current_user': current_user 339 | } 340 | return render(request, 'ecweb/classes.html', context) 341 | 342 | 343 | @login_required 344 | def class_view(request, class_id): 345 | current_user = request.user 346 | class_obj = Class.objects.get(id=class_id) 347 | 348 | choices_student = [] 349 | for student in class_obj.classroom.students.all(): 350 | student_id = student.id 351 | student_name = '{}, {}'.format( 352 | student.user.last_name, student.user.first_name) 353 | choices_student.append((student_id, student_name)) 354 | 355 | if request.method == 'POST': 356 | form = AttendanceForm(request.POST) 357 | form.fields['students'].choices = tuple(choices_student) 358 | 359 | if form.is_valid(): 360 | students_to_update = [int(s) 361 | for s in form.cleaned_data['students']] 362 | class_obj.attendances.clear() 363 | class_obj.attendances.add(*students_to_update) 364 | 365 | return HttpResponseRedirect('/class') 366 | 367 | else: 368 | attendanced_students = [s.id for s in class_obj.attendances.all()] 369 | 370 | form = AttendanceForm( 371 | initial={'class_id': class_id, 'students': attendanced_students}) 372 | form.fields['students'].choices = tuple(choices_student) 373 | 374 | context = { 375 | 'form': form, 376 | 'current_user': current_user, 377 | 'class_id': class_id, 378 | 'class_obj': class_obj 379 | } 380 | return render(request, 'ecweb/class_attendance.html', context) 381 | 382 | 383 | @login_required 384 | def events_list_view(request): 385 | current_user = request.user 386 | events = Event.objects.all() 387 | 388 | return render(request, 'ecweb/events.html', 389 | {'current_user': current_user, 390 | 'events': events}) -------------------------------------------------------------------------------- /ecweb/static/js/libs/tether.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"function"==typeof define&&define.amd?define(e):"object"==typeof exports?module.exports=e(require,exports,module):t.Tether=e()}(this,function(t,e,o){"use strict";function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function n(t){var e=t.getBoundingClientRect(),o={};for(var i in e)o[i]=e[i];if(t.ownerDocument!==document){var r=t.ownerDocument.defaultView.frameElement;if(r){var s=n(r);o.top+=s.top,o.bottom+=s.top,o.left+=s.left,o.right+=s.left}}return o}function r(t){var e=getComputedStyle(t)||{},o=e.position,i=[];if("fixed"===o)return[t];for(var n=t;(n=n.parentNode)&&n&&1===n.nodeType;){var r=void 0;try{r=getComputedStyle(n)}catch(s){}if("undefined"==typeof r||null===r)return i.push(n),i;var a=r,f=a.overflow,l=a.overflowX,h=a.overflowY;/(auto|scroll)/.test(f+h+l)&&("absolute"!==o||["relative","absolute","fixed"].indexOf(r.position)>=0)&&i.push(n)}return i.push(t.ownerDocument.body),t.ownerDocument!==document&&i.push(t.ownerDocument.defaultView),i}function s(){A&&document.body.removeChild(A),A=null}function a(t){var e=void 0;t===document?(e=document,t=document.documentElement):e=t.ownerDocument;var o=e.documentElement,i=n(t),r=P();return i.top-=r.top,i.left-=r.left,"undefined"==typeof i.width&&(i.width=document.body.scrollWidth-i.left-i.right),"undefined"==typeof i.height&&(i.height=document.body.scrollHeight-i.top-i.bottom),i.top=i.top-o.clientTop,i.left=i.left-o.clientLeft,i.right=e.body.clientWidth-i.width-i.left,i.bottom=e.body.clientHeight-i.height-i.top,i}function f(t){return t.offsetParent||document.documentElement}function l(){if(M)return M;var t=document.createElement("div");t.style.width="100%",t.style.height="200px";var e=document.createElement("div");h(e.style,{position:"absolute",top:0,left:0,pointerEvents:"none",visibility:"hidden",width:"200px",height:"150px",overflow:"hidden"}),e.appendChild(t),document.body.appendChild(e);var o=t.offsetWidth;e.style.overflow="scroll";var i=t.offsetWidth;o===i&&(i=e.clientWidth),document.body.removeChild(e);var n=o-i;return M={width:n,height:n}}function h(){var t=arguments.length<=0||void 0===arguments[0]?{}:arguments[0],e=[];return Array.prototype.push.apply(e,arguments),e.slice(1).forEach(function(e){if(e)for(var o in e)({}).hasOwnProperty.call(e,o)&&(t[o]=e[o])}),t}function d(t,e){if("undefined"!=typeof t.classList)e.split(" ").forEach(function(e){e.trim()&&t.classList.remove(e)});else{var o=new RegExp("(^| )"+e.split(" ").join("|")+"( |$)","gi"),i=c(t).replace(o," ");g(t,i)}}function p(t,e){if("undefined"!=typeof t.classList)e.split(" ").forEach(function(e){e.trim()&&t.classList.add(e)});else{d(t,e);var o=c(t)+(" "+e);g(t,o)}}function u(t,e){if("undefined"!=typeof t.classList)return t.classList.contains(e);var o=c(t);return new RegExp("(^| )"+e+"( |$)","gi").test(o)}function c(t){return t.className instanceof t.ownerDocument.defaultView.SVGAnimatedString?t.className.baseVal:t.className}function g(t,e){t.setAttribute("class",e)}function m(t,e,o){o.forEach(function(o){e.indexOf(o)===-1&&u(t,o)&&d(t,o)}),e.forEach(function(e){u(t,e)||p(t,e)})}function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function v(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function y(t,e){var o=arguments.length<=2||void 0===arguments[2]?1:arguments[2];return t+o>=e&&e>=t-o}function b(){return"undefined"!=typeof performance&&"undefined"!=typeof performance.now?performance.now():+new Date}function w(){for(var t={top:0,left:0},e=arguments.length,o=Array(e),i=0;i1?o-1:0),n=1;n16?(e=Math.min(e-16,250),void(o=setTimeout(n,250))):void("undefined"!=typeof t&&b()-t<10||(null!=o&&(clearTimeout(o),o=null),t=b(),X(),e=b()-t))};"undefined"!=typeof window&&"undefined"!=typeof window.addEventListener&&["resize","scroll","touchmove"].forEach(function(t){window.addEventListener(t,i)})}();var F={center:"center",left:"right",right:"left"},H={middle:"middle",top:"bottom",bottom:"top"},N={top:0,left:0,middle:"50%",center:"50%",bottom:"100%",right:"100%"},U=function(t,e){var o=t.left,i=t.top;return"auto"===o&&(o=F[e.left]),"auto"===i&&(i=H[e.top]),{left:o,top:i}},V=function(t){var e=t.left,o=t.top;return"undefined"!=typeof N[t.left]&&(e=N[t.left]),"undefined"!=typeof N[t.top]&&(o=N[t.top]),{left:e,top:o}},R=function(t){var e=t.split(" "),o=z(e,2),i=o[0],n=o[1];return{top:i,left:n}},q=R,I=function(t){function e(t){var o=this;i(this,e),j(Object.getPrototypeOf(e.prototype),"constructor",this).call(this),this.position=this.position.bind(this),D.push(this),this.history=[],this.setOptions(t,!1),x.modules.forEach(function(t){"undefined"!=typeof t.initialize&&t.initialize.call(o)}),this.position()}return v(e,t),E(e,[{key:"getClass",value:function(){var t=arguments.length<=0||void 0===arguments[0]?"":arguments[0],e=this.options.classes;return"undefined"!=typeof e&&e[t]?this.options.classes[t]:this.options.classPrefix?this.options.classPrefix+"-"+t:t}},{key:"setOptions",value:function(t){var e=this,o=arguments.length<=1||void 0===arguments[1]||arguments[1],i={offset:"0 0",targetOffset:"0 0",targetAttachment:"auto auto",classPrefix:"tether"};this.options=h(i,t);var n=this.options,s=n.element,a=n.target,f=n.targetModifier;if(this.element=s,this.target=a,this.targetModifier=f,"viewport"===this.target?(this.target=document.body,this.targetModifier="visible"):"scroll-handle"===this.target&&(this.target=document.body,this.targetModifier="scroll-handle"),["element","target"].forEach(function(t){if("undefined"==typeof e[t])throw new Error("Tether Error: Both element and target must be defined");"undefined"!=typeof e[t].jquery?e[t]=e[t][0]:"string"==typeof e[t]&&(e[t]=document.querySelector(e[t]))}),p(this.element,this.getClass("element")),this.options.addTargetClasses!==!1&&p(this.target,this.getClass("target")),!this.options.attachment)throw new Error("Tether Error: You must provide an attachment");this.targetAttachment=q(this.options.targetAttachment),this.attachment=q(this.options.attachment),this.offset=R(this.options.offset),this.targetOffset=R(this.options.targetOffset),"undefined"!=typeof this.scrollParents&&this.disable(),"scroll-handle"===this.targetModifier?this.scrollParents=[this.target]:this.scrollParents=r(this.target),this.options.enabled!==!1&&this.enable(o)}},{key:"getTargetBounds",value:function(){if("undefined"==typeof this.targetModifier)return a(this.target);if("visible"===this.targetModifier){if(this.target===document.body)return{top:pageYOffset,left:pageXOffset,height:innerHeight,width:innerWidth};var t=a(this.target),e={height:t.height,width:t.width,top:t.top,left:t.left};return e.height=Math.min(e.height,t.height-(pageYOffset-t.top)),e.height=Math.min(e.height,t.height-(t.top+t.height-(pageYOffset+innerHeight))),e.height=Math.min(innerHeight,e.height),e.height-=2,e.width=Math.min(e.width,t.width-(pageXOffset-t.left)),e.width=Math.min(e.width,t.width-(t.left+t.width-(pageXOffset+innerWidth))),e.width=Math.min(innerWidth,e.width),e.width-=2,e.topo.clientWidth||[i.overflow,i.overflowX].indexOf("scroll")>=0||this.target!==document.body,r=0;n&&(r=15);var s=t.height-parseFloat(i.borderTopWidth)-parseFloat(i.borderBottomWidth)-r,e={width:15,height:.975*s*(s/o.scrollHeight),left:t.left+t.width-parseFloat(i.borderLeftWidth)-15},f=0;s<408&&this.target===document.body&&(f=-11e-5*Math.pow(s,2)-.00727*s+22.58),this.target!==document.body&&(e.height=Math.max(e.height,24));var l=this.target.scrollTop/(o.scrollHeight-s);return e.top=l*(s-e.height-f)+t.top+parseFloat(i.borderTopWidth),this.target===document.body&&(e.height=Math.max(e.height,24)),e}}},{key:"clearCache",value:function(){this._cache={}}},{key:"cache",value:function(t,e){return"undefined"==typeof this._cache&&(this._cache={}),"undefined"==typeof this._cache[t]&&(this._cache[t]=e.call(this)),this._cache[t]}},{key:"enable",value:function(){var t=this,e=arguments.length<=0||void 0===arguments[0]||arguments[0];this.options.addTargetClasses!==!1&&p(this.target,this.getClass("enabled")),p(this.element,this.getClass("enabled")),this.enabled=!0,this.scrollParents.forEach(function(e){e!==t.target.ownerDocument&&e.addEventListener("scroll",t.position)}),e&&this.position()}},{key:"disable",value:function(){var t=this;d(this.target,this.getClass("enabled")),d(this.element,this.getClass("enabled")),this.enabled=!1,"undefined"!=typeof this.scrollParents&&this.scrollParents.forEach(function(e){e.removeEventListener("scroll",t.position)})}},{key:"destroy",value:function(){var t=this;this.disable(),D.forEach(function(e,o){e===t&&D.splice(o,1)}),0===D.length&&s()}},{key:"updateAttachClasses",value:function(t,e){var o=this;t=t||this.attachment,e=e||this.targetAttachment;var i=["left","top","bottom","right","middle","center"];"undefined"!=typeof this._addAttachClasses&&this._addAttachClasses.length&&this._addAttachClasses.splice(0,this._addAttachClasses.length),"undefined"==typeof this._addAttachClasses&&(this._addAttachClasses=[]);var n=this._addAttachClasses;t.top&&n.push(this.getClass("element-attached")+"-"+t.top),t.left&&n.push(this.getClass("element-attached")+"-"+t.left),e.top&&n.push(this.getClass("target-attached")+"-"+e.top),e.left&&n.push(this.getClass("target-attached")+"-"+e.left);var r=[];i.forEach(function(t){r.push(o.getClass("element-attached")+"-"+t),r.push(o.getClass("target-attached")+"-"+t)}),k(function(){"undefined"!=typeof o._addAttachClasses&&(m(o.element,o._addAttachClasses,r),o.options.addTargetClasses!==!1&&m(o.target,o._addAttachClasses,r),delete o._addAttachClasses)})}},{key:"position",value:function(){var t=this,e=arguments.length<=0||void 0===arguments[0]||arguments[0];if(this.enabled){this.clearCache();var o=U(this.targetAttachment,this.attachment);this.updateAttachClasses(this.attachment,o);var i=this.cache("element-bounds",function(){return a(t.element)}),n=i.width,r=i.height;if(0===n&&0===r&&"undefined"!=typeof this.lastSize){var s=this.lastSize;n=s.width,r=s.height}else this.lastSize={width:n,height:r};var h=this.cache("target-bounds",function(){return t.getTargetBounds()}),d=h,p=C(V(this.attachment),{width:n,height:r}),u=C(V(o),d),c=C(this.offset,{width:n,height:r}),g=C(this.targetOffset,d);p=w(p,c),u=w(u,g);for(var m=h.left+u.left-p.left,v=h.top+u.top-p.top,y=0;yA.documentElement.clientHeight&&(S=this.cache("scrollbar-size",l),E.viewport.bottom-=S.height),T.innerWidth>A.documentElement.clientWidth&&(S=this.cache("scrollbar-size",l),E.viewport.right-=S.width),["","static"].indexOf(A.body.style.position)!==-1&&["","static"].indexOf(A.body.parentElement.style.position)!==-1||(E.page.bottom=A.body.scrollHeight-v-r,E.page.right=A.body.scrollWidth-m-n),"undefined"!=typeof this.options.optimizations&&this.options.optimizations.moveElement!==!1&&"undefined"==typeof this.targetModifier&&!function(){var e=t.cache("target-offsetparent",function(){return f(t.target)}),o=t.cache("target-offsetparent-bounds",function(){return a(e)}),i=getComputedStyle(e),n=o,r={};if(["Top","Left","Bottom","Right"].forEach(function(t){r[t.toLowerCase()]=parseFloat(i["border"+t+"Width"])}),o.right=A.body.scrollWidth-o.left-n.width+r.right,o.bottom=A.body.scrollHeight-o.top-n.height+r.bottom,E.page.top>=o.top+r.top&&E.page.bottom>=o.bottom&&E.page.left>=o.left+r.left&&E.page.right>=o.right){var s=e.scrollTop,l=e.scrollLeft;E.offset={top:E.page.top-o.top+s-r.top,left:E.page.left-o.left+l-r.left}}}(),this.move(E),this.history.unshift(E),this.history.length>3&&this.history.pop(),e&&_(),!0}}},{key:"move",value:function(t){var e=this;if("undefined"!=typeof this.element.parentNode){var o={};for(var i in t){o[i]={};for(var n in t[i]){for(var r=!1,s=0;s=0){var c=a.split(" "),m=z(c,2);d=m[0],h=m[1]}else h=d=a;var b=O(e,r);"target"!==d&&"both"!==d||(ob[3]&&"bottom"===v.top&&(o-=p,v.top="top")),"together"===d&&("top"===v.top&&("bottom"===y.top&&ob[3]&&o-(s-p)>=b[1]&&(o-=s-p,v.top="bottom",y.top="bottom")),"bottom"===v.top&&("top"===y.top&&o+s>b[3]?(o-=p,v.top="top",o-=s,y.top="bottom"):"bottom"===y.top&&ob[3]&&"top"===y.top?(o-=s,y.top="bottom"):ob[2]&&"right"===v.left&&(i-=u,v.left="left")),"together"===h&&(ib[2]&&"right"===v.left?"left"===y.left?(i-=u,v.left="left",i-=f,y.left="right"):"right"===y.left&&(i-=u,v.left="left",i+=f,y.left="left"):"center"===v.left&&(i+f>b[2]&&"left"===y.left?(i-=f,y.left="right"):ib[3]&&"top"===y.top&&(o-=s,y.top="bottom")),"element"!==h&&"both"!==h||(ib[2]&&("left"===y.left?(i-=f,y.left="right"):"center"===y.left&&(i-=f/2,y.left="right"))),"string"==typeof l?l=l.split(",").map(function(t){return t.trim()}):l===!0&&(l=["top","left","right","bottom"]),l=l||[];var w=[],C=[];o=0?(o=b[1],w.push("top")):C.push("top")),o+s>b[3]&&(l.indexOf("bottom")>=0?(o=b[3]-s,w.push("bottom")):C.push("bottom")),i=0?(i=b[0],w.push("left")):C.push("left")),i+f>b[2]&&(l.indexOf("right")>=0?(i=b[2]-f,w.push("right")):C.push("right")),w.length&&!function(){var t=void 0;t="undefined"!=typeof e.options.pinnedClass?e.options.pinnedClass:e.getClass("pinned"),g.push(t),w.forEach(function(e){g.push(t+"-"+e)})}(),C.length&&!function(){var t=void 0;t="undefined"!=typeof e.options.outOfBoundsClass?e.options.outOfBoundsClass:e.getClass("out-of-bounds"),g.push(t),C.forEach(function(e){g.push(t+"-"+e)})}(),(w.indexOf("left")>=0||w.indexOf("right")>=0)&&(y.left=v.left=!1),(w.indexOf("top")>=0||w.indexOf("bottom")>=0)&&(y.top=v.top=!1),v.top===n.top&&v.left===n.left&&y.top===e.attachment.top&&y.left===e.attachment.left||(e.updateAttachClasses(y,v),e.trigger("update",{attachment:y,targetAttachment:v}))}),k(function(){e.options.addTargetClasses!==!1&&m(e.target,g,c),m(e.element,g,c)}),{top:o,left:i}}});var Y=x.Utils,a=Y.getBounds,m=Y.updateClasses,k=Y.defer;x.modules.push({position:function(t){var e=this,o=t.top,i=t.left,n=this.cache("element-bounds",function(){return a(e.element)}),r=n.height,s=n.width,f=this.getTargetBounds(),l=o+r,h=i+s,d=[];o<=f.bottom&&l>=f.top&&["left","right"].forEach(function(t){var e=f[t];e!==i&&e!==h||d.push(t)}),i<=f.right&&h>=f.left&&["top","bottom"].forEach(function(t){var e=f[t];e!==o&&e!==l||d.push(t)});var p=[],u=[],c=["left","top","right","bottom"];return p.push(this.getClass("abutted")),c.forEach(function(t){p.push(e.getClass("abutted")+"-"+t)}),d.length&&u.push(this.getClass("abutted")),d.forEach(function(t){u.push(e.getClass("abutted")+"-"+t)}),k(function(){e.options.addTargetClasses!==!1&&m(e.target,u,p),m(e.element,u,p)}),!0}});var z=function(){function t(t,e){var o=[],i=!0,n=!1,r=void 0;try{for(var s,a=t[Symbol.iterator]();!(i=(s=a.next()).done)&&(o.push(s.value),!e||o.length!==e);i=!0);}catch(f){n=!0,r=f}finally{try{!i&&a["return"]&&a["return"]()}finally{if(n)throw r}}return o}return function(e,o){if(Array.isArray(e))return e;if(Symbol.iterator in Object(e))return t(e,o);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();return x.modules.push({position:function(t){var e=t.top,o=t.left;if(this.options.shift){var i=this.options.shift;"function"==typeof this.options.shift&&(i=this.options.shift.call(this,{top:e,left:o}));var n=void 0,r=void 0;if("string"==typeof i){i=i.split(" "),i[1]=i[1]||i[0];var s=i,a=z(s,2);n=a[0],r=a[1],n=parseFloat(n,10),r=parseFloat(r,10)}else n=i.top,r=i.left;return e+=n,o+=r,{top:e,left:o}}}}),$}); -------------------------------------------------------------------------------- /ecweb/static/js/libs/perfect-scrollbar.min.js: -------------------------------------------------------------------------------- 1 | /* perfect-scrollbar v0.7.1 */ 2 | !function t(e,n,r){function o(i,s){if(!n[i]){if(!e[i]){var a="function"==typeof require&&require;if(!s&&a)return a(i,!0);if(l)return l(i,!0);var c=new Error("Cannot find module '"+i+"'");throw c.code="MODULE_NOT_FOUND",c}var u=n[i]={exports:{}};e[i][0].call(u.exports,function(t){var n=e[i][1][t];return o(n?n:t)},u,u.exports,t,e,n,r)}return n[i].exports}for(var l="function"==typeof require&&require,i=0;i=0&&n.splice(r,1),t.className=n.join(" ")}n.add=function(t,e){t.classList?t.classList.add(e):r(t,e)},n.remove=function(t,e){t.classList?t.classList.remove(e):o(t,e)},n.list=function(t){return t.classList?Array.prototype.slice.apply(t.classList):t.className.split(" ")}},{}],3:[function(t,e,n){"use strict";function r(t,e){return window.getComputedStyle(t)[e]}function o(t,e,n){return"number"==typeof n&&(n=n.toString()+"px"),t.style[e]=n,t}function l(t,e){for(var n in e){var r=e[n];"number"==typeof r&&(r=r.toString()+"px"),t.style[n]=r}return t}var i={};i.e=function(t,e){var n=document.createElement(t);return n.className=e,n},i.appendTo=function(t,e){return e.appendChild(t),t},i.css=function(t,e,n){return"object"==typeof e?l(t,e):"undefined"==typeof n?r(t,e):o(t,e,n)},i.matches=function(t,e){return"undefined"!=typeof t.matches?t.matches(e):"undefined"!=typeof t.matchesSelector?t.matchesSelector(e):"undefined"!=typeof t.webkitMatchesSelector?t.webkitMatchesSelector(e):"undefined"!=typeof t.mozMatchesSelector?t.mozMatchesSelector(e):"undefined"!=typeof t.msMatchesSelector?t.msMatchesSelector(e):void 0},i.remove=function(t){"undefined"!=typeof t.remove?t.remove():t.parentNode&&t.parentNode.removeChild(t)},i.queryChildren=function(t,e){return Array.prototype.filter.call(t.childNodes,function(t){return i.matches(t,e)})},e.exports=i},{}],4:[function(t,e,n){"use strict";var r=function(t){this.element=t,this.events={}};r.prototype.bind=function(t,e){"undefined"==typeof this.events[t]&&(this.events[t]=[]),this.events[t].push(e),this.element.addEventListener(t,e,!1)},r.prototype.unbind=function(t,e){var n="undefined"!=typeof e;this.events[t]=this.events[t].filter(function(r){return n&&r!==e?!0:(this.element.removeEventListener(t,r,!1),!1)},this)},r.prototype.unbindAll=function(){for(var t in this.events)this.unbind(t)};var o=function(){this.eventElements=[]};o.prototype.eventElement=function(t){var e=this.eventElements.filter(function(e){return e.element===t})[0];return"undefined"==typeof e&&(e=new r(t),this.eventElements.push(e)),e},o.prototype.bind=function(t,e,n){this.eventElement(t).bind(e,n)},o.prototype.unbind=function(t,e,n){this.eventElement(t).unbind(e,n)},o.prototype.unbindAll=function(){for(var t=0;te.scrollbarYTop?1:-1;i(t,"top",t.scrollTop+s*e.containerHeight),l(t),r.stopPropagation()}),e.event.bind(e.scrollbarX,"click",r),e.event.bind(e.scrollbarXRail,"click",function(r){var o=r.pageX-window.pageXOffset-n(e.scrollbarXRail).left,s=o>e.scrollbarXLeft?1:-1;i(t,"left",t.scrollLeft+s*e.containerWidth),l(t),r.stopPropagation()})}var o=t("../instances"),l=t("../update-geometry"),i=t("../update-scroll");e.exports=function(t){var e=o.get(t);r(t,e)}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],11:[function(t,e,n){"use strict";function r(t,e){function n(n){var o=r+n*e.railXRatio,i=Math.max(0,e.scrollbarXRail.getBoundingClientRect().left)+e.railXRatio*(e.railXWidth-e.scrollbarXWidth);0>o?e.scrollbarXLeft=0:o>i?e.scrollbarXLeft=i:e.scrollbarXLeft=o;var s=l.toInt(e.scrollbarXLeft*(e.contentWidth-e.containerWidth)/(e.containerWidth-e.railXRatio*e.scrollbarXWidth))-e.negativeScrollAdjustment;c(t,"left",s)}var r=null,o=null,s=function(e){n(e.pageX-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"x"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarX,"mousedown",function(n){o=n.pageX,r=l.toInt(i.css(e.scrollbarX,"left"))*e.railXRatio,l.startScrolling(t,"x"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}function o(t,e){function n(n){var o=r+n*e.railYRatio,i=Math.max(0,e.scrollbarYRail.getBoundingClientRect().top)+e.railYRatio*(e.railYHeight-e.scrollbarYHeight);0>o?e.scrollbarYTop=0:o>i?e.scrollbarYTop=i:e.scrollbarYTop=o;var s=l.toInt(e.scrollbarYTop*(e.contentHeight-e.containerHeight)/(e.containerHeight-e.railYRatio*e.scrollbarYHeight));c(t,"top",s)}var r=null,o=null,s=function(e){n(e.pageY-o),a(t),e.stopPropagation(),e.preventDefault()},u=function(){l.stopScrolling(t,"y"),e.event.unbind(e.ownerDocument,"mousemove",s)};e.event.bind(e.scrollbarY,"mousedown",function(n){o=n.pageY,r=l.toInt(i.css(e.scrollbarY,"top"))*e.railYRatio,l.startScrolling(t,"y"),e.event.bind(e.ownerDocument,"mousemove",s),e.event.once(e.ownerDocument,"mouseup",u),n.stopPropagation(),n.preventDefault()})}var l=t("../../lib/helper"),i=t("../../lib/dom"),s=t("../instances"),a=t("../update-geometry"),c=t("../update-scroll");e.exports=function(t){var e=s.get(t);r(t,e),o(t,e)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],12:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var l=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===l&&0>n||l>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}var r=!1;e.event.bind(t,"mouseenter",function(){r=!0}),e.event.bind(t,"mouseleave",function(){r=!1});var i=!1;e.event.bind(e.ownerDocument,"keydown",function(c){if(!(c.isDefaultPrevented&&c.isDefaultPrevented()||c.defaultPrevented)){var u=l.matches(e.scrollbarX,":focus")||l.matches(e.scrollbarY,":focus");if(r||u){var d=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(d){if("IFRAME"===d.tagName)d=d.contentDocument.activeElement;else for(;d.shadowRoot;)d=d.shadowRoot.activeElement;if(o.isEditable(d))return}var p=0,f=0;switch(c.which){case 37:p=c.metaKey?-e.contentWidth:c.altKey?-e.containerWidth:-30;break;case 38:f=c.metaKey?e.contentHeight:c.altKey?e.containerHeight:30;break;case 39:p=c.metaKey?e.contentWidth:c.altKey?e.containerWidth:30;break;case 40:f=c.metaKey?-e.contentHeight:c.altKey?-e.containerHeight:-30;break;case 33:f=90;break;case 32:f=c.shiftKey?90:-90;break;case 34:f=-90;break;case 35:f=c.ctrlKey?-e.contentHeight:-e.containerHeight;break;case 36:f=c.ctrlKey?t.scrollTop:e.containerHeight;break;default:return}a(t,"top",t.scrollTop-f),a(t,"left",t.scrollLeft+p),s(t),i=n(p,f),i&&c.preventDefault()}}})}var o=t("../../lib/helper"),l=t("../../lib/dom"),i=t("../instances"),s=t("../update-geometry"),a=t("../update-scroll");e.exports=function(t){var e=i.get(t);r(t,e)}},{"../../lib/dom":3,"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],13:[function(t,e,n){"use strict";function r(t,e){function n(n,r){var o=t.scrollTop;if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&0>r)return!e.settings.wheelPropagation}var l=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===l&&0>n||l>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}function r(t){var e=t.deltaX,n=-1*t.deltaY;return"undefined"!=typeof e&&"undefined"!=typeof n||(e=-1*t.wheelDeltaX/6,n=t.wheelDeltaY/6),t.deltaMode&&1===t.deltaMode&&(e*=10,n*=10),e!==e&&n!==n&&(e=0,n=t.wheelDelta),t.shiftKey?[-n,-e]:[e,n]}function o(e,n){var r=t.querySelector("textarea:hover, select[multiple]:hover, .ps-child:hover");if(r){var o=window.getComputedStyle(r),l=[o.overflow,o.overflowX,o.overflowY].join("");if(!l.match(/(scroll|auto)/))return!1;var i=r.scrollHeight-r.clientHeight;if(i>0&&!(0===r.scrollTop&&n>0||r.scrollTop===i&&0>n))return!0;var s=r.scrollLeft-r.clientWidth;if(s>0&&!(0===r.scrollLeft&&0>e||r.scrollLeft===s&&e>0))return!0}return!1}function s(s){var c=r(s),u=c[0],d=c[1];o(u,d)||(a=!1,e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(d?i(t,"top",t.scrollTop-d*e.settings.wheelSpeed):i(t,"top",t.scrollTop+u*e.settings.wheelSpeed),a=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(u?i(t,"left",t.scrollLeft+u*e.settings.wheelSpeed):i(t,"left",t.scrollLeft-d*e.settings.wheelSpeed),a=!0):(i(t,"top",t.scrollTop-d*e.settings.wheelSpeed),i(t,"left",t.scrollLeft+u*e.settings.wheelSpeed)),l(t),a=a||n(u,d),a&&(s.stopPropagation(),s.preventDefault()))}var a=!1;"undefined"!=typeof window.onwheel?e.event.bind(t,"wheel",s):"undefined"!=typeof window.onmousewheel&&e.event.bind(t,"mousewheel",s)}var o=t("../instances"),l=t("../update-geometry"),i=t("../update-scroll");e.exports=function(t){var e=o.get(t);r(t,e)}},{"../instances":18,"../update-geometry":19,"../update-scroll":20}],14:[function(t,e,n){"use strict";function r(t,e){e.event.bind(t,"scroll",function(){l(t)})}var o=t("../instances"),l=t("../update-geometry");e.exports=function(t){var e=o.get(t);r(t,e)}},{"../instances":18,"../update-geometry":19}],15:[function(t,e,n){"use strict";function r(t,e){function n(){var t=window.getSelection?window.getSelection():document.getSelection?document.getSelection():"";return 0===t.toString().length?null:t.getRangeAt(0).commonAncestorContainer}function r(){c||(c=setInterval(function(){return l.get(t)?(s(t,"top",t.scrollTop+u.top),s(t,"left",t.scrollLeft+u.left),void i(t)):void clearInterval(c)},50))}function a(){c&&(clearInterval(c),c=null),o.stopScrolling(t)}var c=null,u={top:0,left:0},d=!1;e.event.bind(e.ownerDocument,"selectionchange",function(){t.contains(n())?d=!0:(d=!1,a())}),e.event.bind(window,"mouseup",function(){d&&(d=!1,a())}),e.event.bind(window,"keyup",function(){d&&(d=!1,a())}),e.event.bind(window,"mousemove",function(e){if(d){var n={x:e.pageX,y:e.pageY},l={left:t.offsetLeft,right:t.offsetLeft+t.offsetWidth,top:t.offsetTop,bottom:t.offsetTop+t.offsetHeight};n.xl.right-3?(u.left=5,o.startScrolling(t,"x")):u.left=0,n.yl.bottom-3?(n.y-l.bottom+3<5?u.top=5:u.top=20,o.startScrolling(t,"y")):u.top=0,0===u.top&&0===u.left?a():r()}})}var o=t("../../lib/helper"),l=t("../instances"),i=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){var e=l.get(t);r(t,e)}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],16:[function(t,e,n){"use strict";function r(t,e,n,r){function o(n,r){var o=t.scrollTop,l=t.scrollLeft,i=Math.abs(n),s=Math.abs(r);if(s>i){if(0>r&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return!e.settings.swipePropagation}else if(i>s&&(0>n&&l===e.contentWidth-e.containerWidth||n>0&&0===l))return!e.settings.swipePropagation;return!0}function a(e,n){s(t,"top",t.scrollTop-n),s(t,"left",t.scrollLeft-e),i(t)}function c(){w=!0}function u(){w=!1}function d(t){return t.targetTouches?t.targetTouches[0]:t}function p(t){return t.targetTouches&&1===t.targetTouches.length?!0:!(!t.pointerType||"mouse"===t.pointerType||t.pointerType===t.MSPOINTER_TYPE_MOUSE)}function f(t){if(p(t)){Y=!0;var e=d(t);g.pageX=e.pageX,g.pageY=e.pageY,v=(new Date).getTime(),null!==y&&clearInterval(y),t.stopPropagation()}}function h(t){if(!Y&&e.settings.swipePropagation&&f(t),!w&&Y&&p(t)){var n=d(t),r={pageX:n.pageX,pageY:n.pageY},l=r.pageX-g.pageX,i=r.pageY-g.pageY;a(l,i),g=r;var s=(new Date).getTime(),c=s-v;c>0&&(m.x=l/c,m.y=i/c,v=s),o(l,i)&&(t.stopPropagation(),t.preventDefault())}}function b(){!w&&Y&&(Y=!1,e.settings.swipeEasing&&(clearInterval(y),y=setInterval(function(){return l.get(t)&&(m.x||m.y)?Math.abs(m.x)<.01&&Math.abs(m.y)<.01?void clearInterval(y):(a(30*m.x,30*m.y),m.x*=.8,void(m.y*=.8)):void clearInterval(y)},10)))}var g={},v=0,m={},y=null,w=!1,Y=!1;n?(e.event.bind(window,"touchstart",c),e.event.bind(window,"touchend",u),e.event.bind(t,"touchstart",f),e.event.bind(t,"touchmove",h),e.event.bind(t,"touchend",b)):r&&(window.PointerEvent?(e.event.bind(window,"pointerdown",c),e.event.bind(window,"pointerup",u),e.event.bind(t,"pointerdown",f),e.event.bind(t,"pointermove",h),e.event.bind(t,"pointerup",b)):window.MSPointerEvent&&(e.event.bind(window,"MSPointerDown",c),e.event.bind(window,"MSPointerUp",u),e.event.bind(t,"MSPointerDown",f),e.event.bind(t,"MSPointerMove",h),e.event.bind(t,"MSPointerUp",b)))}var o=t("../../lib/helper"),l=t("../instances"),i=t("../update-geometry"),s=t("../update-scroll");e.exports=function(t){if(o.env.supportsTouch||o.env.supportsIePointer){var e=l.get(t);r(t,e,o.env.supportsTouch,o.env.supportsIePointer)}}},{"../../lib/helper":6,"../instances":18,"../update-geometry":19,"../update-scroll":20}],17:[function(t,e,n){"use strict";var r=t("../lib/helper"),o=t("../lib/class"),l=t("./instances"),i=t("./update-geometry"),s={"click-rail":t("./handler/click-rail"),"drag-scrollbar":t("./handler/drag-scrollbar"),keyboard:t("./handler/keyboard"),wheel:t("./handler/mouse-wheel"),touch:t("./handler/touch"),selection:t("./handler/selection")},a=t("./handler/native-scroll");e.exports=function(t,e){e="object"==typeof e?e:{},o.add(t,"ps");var n=l.add(t);n.settings=r.extend(n.settings,e),o.add(t,"ps--theme_"+n.settings.theme),n.settings.handlers.forEach(function(e){s[e](t)}),a(t),i(t)}},{"../lib/class":2,"../lib/helper":6,"./handler/click-rail":10,"./handler/drag-scrollbar":11,"./handler/keyboard":12,"./handler/mouse-wheel":13,"./handler/native-scroll":14,"./handler/selection":15,"./handler/touch":16,"./instances":18,"./update-geometry":19}],18:[function(t,e,n){"use strict";function r(t){function e(){a.add(t,"ps--focus")}function n(){a.remove(t,"ps--focus")}var r=this;r.settings=s.clone(c),r.containerWidth=null,r.containerHeight=null,r.contentWidth=null,r.contentHeight=null,r.isRtl="rtl"===u.css(t,"direction"),r.isNegativeScroll=function(){var e=t.scrollLeft,n=null;return t.scrollLeft=-1,n=t.scrollLeft<0,t.scrollLeft=e,n}(),r.negativeScrollAdjustment=r.isNegativeScroll?t.scrollWidth-t.clientWidth:0,r.event=new d,r.ownerDocument=t.ownerDocument||document,r.scrollbarXRail=u.appendTo(u.e("div","ps__scrollbar-x-rail"),t),r.scrollbarX=u.appendTo(u.e("div","ps__scrollbar-x"),r.scrollbarXRail),r.scrollbarX.setAttribute("tabindex",0),r.event.bind(r.scrollbarX,"focus",e),r.event.bind(r.scrollbarX,"blur",n),r.scrollbarXActive=null,r.scrollbarXWidth=null,r.scrollbarXLeft=null,r.scrollbarXBottom=s.toInt(u.css(r.scrollbarXRail,"bottom")),r.isScrollbarXUsingBottom=r.scrollbarXBottom===r.scrollbarXBottom,r.scrollbarXTop=r.isScrollbarXUsingBottom?null:s.toInt(u.css(r.scrollbarXRail,"top")),r.railBorderXWidth=s.toInt(u.css(r.scrollbarXRail,"borderLeftWidth"))+s.toInt(u.css(r.scrollbarXRail,"borderRightWidth")),u.css(r.scrollbarXRail,"display","block"),r.railXMarginWidth=s.toInt(u.css(r.scrollbarXRail,"marginLeft"))+s.toInt(u.css(r.scrollbarXRail,"marginRight")),u.css(r.scrollbarXRail,"display",""),r.railXWidth=null,r.railXRatio=null,r.scrollbarYRail=u.appendTo(u.e("div","ps__scrollbar-y-rail"),t),r.scrollbarY=u.appendTo(u.e("div","ps__scrollbar-y"),r.scrollbarYRail),r.scrollbarY.setAttribute("tabindex",0),r.event.bind(r.scrollbarY,"focus",e),r.event.bind(r.scrollbarY,"blur",n),r.scrollbarYActive=null,r.scrollbarYHeight=null,r.scrollbarYTop=null,r.scrollbarYRight=s.toInt(u.css(r.scrollbarYRail,"right")),r.isScrollbarYUsingRight=r.scrollbarYRight===r.scrollbarYRight,r.scrollbarYLeft=r.isScrollbarYUsingRight?null:s.toInt(u.css(r.scrollbarYRail,"left")),r.scrollbarYOuterWidth=r.isRtl?s.outerWidth(r.scrollbarY):null,r.railBorderYWidth=s.toInt(u.css(r.scrollbarYRail,"borderTopWidth"))+s.toInt(u.css(r.scrollbarYRail,"borderBottomWidth")),u.css(r.scrollbarYRail,"display","block"),r.railYMarginHeight=s.toInt(u.css(r.scrollbarYRail,"marginTop"))+s.toInt(u.css(r.scrollbarYRail,"marginBottom")),u.css(r.scrollbarYRail,"display",""),r.railYHeight=null,r.railYRatio=null}function o(t){return t.getAttribute("data-ps-id")}function l(t,e){t.setAttribute("data-ps-id",e)}function i(t){t.removeAttribute("data-ps-id")}var s=t("../lib/helper"),a=t("../lib/class"),c=t("./default-setting"),u=t("../lib/dom"),d=t("../lib/event-manager"),p=t("../lib/guid"),f={};n.add=function(t){var e=p();return l(t,e),f[e]=new r(t),f[e]},n.remove=function(t){delete f[o(t)],i(t)},n.get=function(t){return f[o(t)]}},{"../lib/class":2,"../lib/dom":3,"../lib/event-manager":4,"../lib/guid":5,"../lib/helper":6,"./default-setting":8}],19:[function(t,e,n){"use strict";function r(t,e){return t.settings.minScrollbarLength&&(e=Math.max(e,t.settings.minScrollbarLength)),t.settings.maxScrollbarLength&&(e=Math.min(e,t.settings.maxScrollbarLength)),e}function o(t,e){var n={width:e.railXWidth};e.isRtl?n.left=e.negativeScrollAdjustment+t.scrollLeft+e.containerWidth-e.contentWidth:n.left=t.scrollLeft,e.isScrollbarXUsingBottom?n.bottom=e.scrollbarXBottom-t.scrollTop:n.top=e.scrollbarXTop+t.scrollTop,s.css(e.scrollbarXRail,n);var r={top:t.scrollTop,height:e.railYHeight};e.isScrollbarYUsingRight?e.isRtl?r.right=e.contentWidth-(e.negativeScrollAdjustment+t.scrollLeft)-e.scrollbarYRight-e.scrollbarYOuterWidth:r.right=e.scrollbarYRight-t.scrollLeft:e.isRtl?r.left=e.negativeScrollAdjustment+t.scrollLeft+2*e.containerWidth-e.contentWidth-e.scrollbarYLeft-e.scrollbarYOuterWidth:r.left=e.scrollbarYLeft+t.scrollLeft,s.css(e.scrollbarYRail,r),s.css(e.scrollbarX,{left:e.scrollbarXLeft,width:e.scrollbarXWidth-e.railBorderXWidth}),s.css(e.scrollbarY,{top:e.scrollbarYTop,height:e.scrollbarYHeight-e.railBorderYWidth})}var l=t("../lib/helper"),i=t("../lib/class"),s=t("../lib/dom"),a=t("./instances"),c=t("./update-scroll");e.exports=function(t){var e=a.get(t);e.containerWidth=t.clientWidth,e.containerHeight=t.clientHeight,e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight;var n;t.contains(e.scrollbarXRail)||(n=s.queryChildren(t,".ps__scrollbar-x-rail"),n.length>0&&n.forEach(function(t){s.remove(t)}),s.appendTo(e.scrollbarXRail,t)),t.contains(e.scrollbarYRail)||(n=s.queryChildren(t,".ps__scrollbar-y-rail"),n.length>0&&n.forEach(function(t){s.remove(t)}),s.appendTo(e.scrollbarYRail,t)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),o(t,e),e.scrollbarXActive?i.add(t,"ps--active-x"):(i.remove(t,"ps--active-x"),e.scrollbarXWidth=0,e.scrollbarXLeft=0,c(t,"left",0)),e.scrollbarYActive?i.add(t,"ps--active-y"):(i.remove(t,"ps--active-y"),e.scrollbarYHeight=0,e.scrollbarYTop=0,c(t,"top",0))}},{"../lib/class":2,"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-scroll":20}],20:[function(t,e,n){"use strict";var r=t("./instances"),o=function(t){var e=document.createEvent("Event");return e.initEvent(t,!0,!0),e};e.exports=function(t,e,n){if("undefined"==typeof t)throw"You must provide an element to the update-scroll function";if("undefined"==typeof e)throw"You must provide an axis to the update-scroll function";if("undefined"==typeof n)throw"You must provide a value to the update-scroll function";"top"===e&&0>=n&&(t.scrollTop=n=0,t.dispatchEvent(o("ps-y-reach-start"))),"left"===e&&0>=n&&(t.scrollLeft=n=0,t.dispatchEvent(o("ps-x-reach-start")));var l=r.get(t);"top"===e&&n>=l.contentHeight-l.containerHeight&&(n=l.contentHeight-l.containerHeight,n-t.scrollTop<=1?n=t.scrollTop:t.scrollTop=n,t.dispatchEvent(o("ps-y-reach-end"))),"left"===e&&n>=l.contentWidth-l.containerWidth&&(n=l.contentWidth-l.containerWidth,n-t.scrollLeft<=1?n=t.scrollLeft:t.scrollLeft=n,t.dispatchEvent(o("ps-x-reach-end"))),void 0===l.lastTop&&(l.lastTop=t.scrollTop),void 0===l.lastLeft&&(l.lastLeft=t.scrollLeft),"top"===e&&nl.lastTop&&t.dispatchEvent(o("ps-scroll-down")),"left"===e&&nl.lastLeft&&t.dispatchEvent(o("ps-scroll-right")),"top"===e&&n!==l.lastTop&&(t.scrollTop=l.lastTop=n,t.dispatchEvent(o("ps-scroll-y"))),"left"===e&&n!==l.lastLeft&&(t.scrollLeft=l.lastLeft=n,t.dispatchEvent(o("ps-scroll-x")))}},{"./instances":18}],21:[function(t,e,n){"use strict";var r=t("../lib/helper"),o=t("../lib/dom"),l=t("./instances"),i=t("./update-geometry"),s=t("./update-scroll");e.exports=function(t){var e=l.get(t);e&&(e.negativeScrollAdjustment=e.isNegativeScroll?t.scrollWidth-t.clientWidth:0,o.css(e.scrollbarXRail,"display","block"),o.css(e.scrollbarYRail,"display","block"),e.railXMarginWidth=r.toInt(o.css(e.scrollbarXRail,"marginLeft"))+r.toInt(o.css(e.scrollbarXRail,"marginRight")),e.railYMarginHeight=r.toInt(o.css(e.scrollbarYRail,"marginTop"))+r.toInt(o.css(e.scrollbarYRail,"marginBottom")),o.css(e.scrollbarXRail,"display","none"),o.css(e.scrollbarYRail,"display","none"),i(t),s(t,"top",t.scrollTop),s(t,"left",t.scrollLeft),o.css(e.scrollbarXRail,"display",""),o.css(e.scrollbarYRail,"display",""))}},{"../lib/dom":3,"../lib/helper":6,"./instances":18,"./update-geometry":19,"./update-scroll":20}]},{},[1]); --------------------------------------------------------------------------------