├── src └── student_network │ ├── __init__.py │ ├── views │ ├── __init__.py │ ├── chat.py │ ├── staff.py │ └── achievements.py │ ├── static │ ├── images │ │ ├── GSEP_ToS.png │ │ ├── default-pfp.jpg │ │ ├── logo │ │ │ ├── text_jpg.jpg │ │ │ ├── text_png.png │ │ │ ├── chatboxes_jpg.jpg │ │ │ ├── chatboxes_png.png │ │ │ ├── full_logo_jpg.jpg │ │ │ ├── full_logo_png.png │ │ │ ├── text_stretch_jpg.jpg │ │ │ ├── text_stretch_png.png │ │ │ ├── chatboxes_stretch_jpg.jpg │ │ │ ├── chatboxes_stretch_png.png │ │ │ ├── full_logo_stretch_jpg.jpg │ │ │ ├── full_logo_stretch_png.png │ │ │ └── chatboxes_stretch_png_square.png │ │ ├── blue_background.png │ │ ├── reconnect_tile_background.png │ │ ├── avatars │ │ │ ├── 20ce965d-68aa-488f-b2ee-159b5c8b339c.jpg │ │ │ ├── 2a607ae9-d4ab-4c34-bd59-7560f338302a.jpg │ │ │ ├── 3a84ceae-1643-4258-93ee-ce04be4a61a3.jpg │ │ │ ├── 3b9ab10f-cd01-4c12-9dce-b0cf38281e11.jpg │ │ │ ├── 475a1eeb-64f6-4577-ba0b-be4e5fea06fd.jpg │ │ │ ├── 477256f5-ecfd-4441-b7e7-554163c47dc6.jpg │ │ │ ├── 48615484-731c-47e2-bcc6-185f64c3c239.jpg │ │ │ ├── 49b44c64-65e3-4e8a-850c-04ce10ebc581.jpg │ │ │ ├── 5e8405fa-0940-44da-9908-e7e077c67977.jpg │ │ │ ├── 656629aa-dc27-489d-9af2-51913758f32b.jpg │ │ │ ├── 683dcfe2-e014-49b3-987f-3ade3f30305d.jpg │ │ │ ├── 8ae6f094-09c1-4d9a-b9fd-1ad12a8fcc5a.jpg │ │ │ ├── 8d1468c8-411a-4cbc-b1e4-2062761c98ce.jpg │ │ │ ├── 9068dbb4-7d17-44a2-9ccb-8b0d878c8c83.jpg │ │ │ ├── a7c9f14d-4de6-4735-9b16-13e1e3df0ca4.jpg │ │ │ ├── b3849c93-9baf-4366-8f9a-cb3609444d41.jpg │ │ │ ├── e050510d-b544-42ba-bcd0-d75a1089b7e2.jpg │ │ │ └── ea76ef51-d05e-455a-8bca-1dc2a7c82a4a.jpg │ │ └── post_imgs │ │ │ ├── 13ecaa90-d72f-4283-bb8e-726eafe79aa7.jpg │ │ │ ├── 33d7749f-6031-4a0a-b252-3d7a8ae64331.jpg │ │ │ ├── 54544cbe-a346-404a-b71e-80ab4f641f76.jpg │ │ │ ├── 70dc8871-e424-454e-85bc-ac02ac9f834f.jpg │ │ │ ├── 78afe457-39ba-4bed-b5cd-8e1fa8db227e.jpg │ │ │ ├── 877a3c17-2be4-4b21-a2b5-58d824aac62f.jpg │ │ │ ├── af2ecd76-c9c4-4e1f-8153-498f2abb5ecc.jpg │ │ │ ├── b76ff2d3-a292-4270-99a4-d4801321d626.jpg │ │ │ ├── ba4aa665-94c9-44e9-a171-c0f797ee77a4.jpg │ │ │ ├── c818c247-0fec-4a21-b2ca-f99ee7a2c903.jpg │ │ │ ├── d8a2f51b-14b8-42cd-a461-a06c79ed4833.jpg │ │ │ ├── eb3469c7-5a5b-4187-9e5c-c39cc884cbfc.jpg │ │ │ ├── f0f068ef-c325-49f4-831f-3d5129e26788.jpg │ │ │ └── f98c6507-7755-435a-90f1-ade8f618781c.jpg │ ├── FormatBody.js │ ├── searchbar.js │ ├── hobbies.js │ ├── interests.js │ ├── slideshow.js │ └── styles │ │ ├── mobile.css │ │ └── slideshow.css │ ├── templates │ ├── error.html │ ├── quiz_results.html │ ├── admin.html │ ├── login.html │ ├── flashcards_set.html │ ├── leaderboard.html │ ├── home_page.html │ ├── flashcards_view.html │ ├── flashcards_play.html │ ├── privacy_policy.html │ ├── flashcards_edit.html │ ├── members.html │ ├── terms.html │ ├── register.html │ ├── quiz.html │ ├── request.html │ ├── achievements.html │ ├── chat.html │ ├── base.html │ └── quizzes.html │ ├── app.py │ └── helpers │ ├── helper_login.py │ ├── helper_profile.py │ └── helper_general.py ├── db.sqlite3 ├── .prettierignore ├── docs ├── designs │ ├── poster.pdf │ ├── Register Page UI Concept.png │ ├── django_test │ │ ├── mysite │ │ │ ├── poll │ │ │ │ ├── apps.py │ │ │ │ ├── admin.py │ │ │ │ ├── static │ │ │ │ │ └── poll │ │ │ │ │ │ ├── images │ │ │ │ │ │ └── background.gif │ │ │ │ │ │ └── style.css │ │ │ │ ├── templates │ │ │ │ │ └── poll │ │ │ │ │ │ ├── results.html │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ └── detail.html │ │ │ │ ├── models.py │ │ │ │ ├── urls.py │ │ │ │ ├── migrations │ │ │ │ │ └── 0001_initial.py │ │ │ │ ├── views.py │ │ │ │ └── tests.py │ │ │ ├── mysite │ │ │ │ ├── asgi.py │ │ │ │ ├── wsgi.py │ │ │ │ ├── urls.py │ │ │ │ └── settings.py │ │ │ └── manage.py │ │ └── README.md │ ├── Version Release Cycle Plan (v0.1-2.0).png │ ├── Version Release Cycle Plan (v2.1-3.0).png │ ├── database test │ │ ├── database-test.py │ │ └── templates │ │ │ └── index.html │ └── Achievements table reference.csv ├── legal │ └── GSEP_privacy_policy.pdf ├── test-log │ └── Test Documentation.docx ├── kanban-snapshots │ ├── Kanban Snapshot 10.jpg │ ├── Kanban Snapshot 11.jpg │ ├── Kanban Snapshot 12.jpg │ ├── Kanban Snapshot 5.jpg │ ├── Kanban Snapshot 6.jpg │ ├── Kanban Snapshot 7.jpg │ ├── Kanban Snapshot 8.jpg │ ├── Kanban Snapshot 9.jpg │ ├── Kanban Snapshot - 11th Feb.jpg │ ├── Kanban Snapshot - 10th Feb (1).jpg │ ├── Kanban Snapshot - 10th Feb (2).jpg │ └── Kanban Snapshot - 10th Feb (3).jpg ├── requirements-analysis │ ├── MoSCoW Matrix.pdf │ ├── Analysis - Success Criteria.docx │ ├── Group N - Social Media Survey.pdf │ ├── Research - Back-End Solutions.docx │ ├── Research - Front-End Solutions.docx │ ├── Plan - Design Thinking and MoSCoW Matrix.docx │ └── Social Media Survey - Group N (Responses) (1).xlsx └── meeting-notes │ ├── Minutes - Initial Meeting.docx │ ├── Minutes - Follow-Up Meeting.docx │ ├── Minutes - Initial Backlog Meeting.docx │ ├── Minutes - Client Meeting (22nd Feb).docx │ ├── Minutes - Client Meeting (8th Feb).docx │ ├── Minutes - Stand-Up Meeting (12th Feb).docx │ ├── Minutes - Stand-Up Meeting (15th Feb).docx │ ├── Minutes - Stand-Up Meeting (19th Feb).docx │ ├── Minutes - Stand-Up Meeting (21st Feb).docx │ ├── Minutes - Stand-Up Meeting (22nd Feb).docx │ ├── Minutes - Stand-Up Meeting (24th Feb).docx │ ├── Minutes - Stand-Up Meeting (26th Feb).docx │ ├── Minutes - Stand-Up Meeting (3rd March).docx │ ├── Minutes - Stand-Up Meeting (5th March).docx │ ├── Minutes - Stand-Up Meeting (7th March).docx │ └── Minutes - Version Release Diagram Meeting.docx ├── setup.py ├── prototypes ├── password_hash.py ├── create_database.py └── registration.py ├── requirements.txt ├── tests ├── test_general.py ├── test_social_profiles.py └── test_login_system.py ├── utils └── change_demo_account_passwords.py ├── .github └── workflows │ ├── main.yml │ └── codeql-analysis.yml ├── CONTRIBUTING.md ├── .gitignore └── README.md /src/student_network/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/student_network/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/db.sqlite3 -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore styles as they're from external sources. 2 | src/student_network/static/styles -------------------------------------------------------------------------------- /docs/designs/poster.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/designs/poster.pdf -------------------------------------------------------------------------------- /docs/legal/GSEP_privacy_policy.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/legal/GSEP_privacy_policy.pdf -------------------------------------------------------------------------------- /docs/test-log/Test Documentation.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/test-log/Test Documentation.docx -------------------------------------------------------------------------------- /docs/designs/Register Page UI Concept.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/designs/Register Page UI Concept.png -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PollConfig(AppConfig): 5 | name = "poll" 6 | -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 10.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 11.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 12.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 5.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 6.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 7.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 8.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot 9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot 9.jpg -------------------------------------------------------------------------------- /docs/requirements-analysis/MoSCoW Matrix.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/requirements-analysis/MoSCoW Matrix.pdf -------------------------------------------------------------------------------- /src/student_network/static/images/GSEP_ToS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/GSEP_ToS.png -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Question 4 | 5 | admin.site.register(Question) 6 | -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Initial Meeting.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Initial Meeting.docx -------------------------------------------------------------------------------- /src/student_network/static/images/default-pfp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/default-pfp.jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot - 11th Feb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot - 11th Feb.jpg -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Follow-Up Meeting.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Follow-Up Meeting.docx -------------------------------------------------------------------------------- /src/student_network/static/images/logo/text_jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/text_jpg.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/logo/text_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/text_png.png -------------------------------------------------------------------------------- /docs/designs/Version Release Cycle Plan (v0.1-2.0).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/designs/Version Release Cycle Plan (v0.1-2.0).png -------------------------------------------------------------------------------- /docs/designs/Version Release Cycle Plan (v2.1-3.0).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/designs/Version Release Cycle Plan (v2.1-3.0).png -------------------------------------------------------------------------------- /src/student_network/static/images/blue_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/blue_background.png -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot - 10th Feb (1).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot - 10th Feb (1).jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot - 10th Feb (2).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot - 10th Feb (2).jpg -------------------------------------------------------------------------------- /docs/kanban-snapshots/Kanban Snapshot - 10th Feb (3).jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/kanban-snapshots/Kanban Snapshot - 10th Feb (3).jpg -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Initial Backlog Meeting.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Initial Backlog Meeting.docx -------------------------------------------------------------------------------- /src/student_network/static/images/logo/chatboxes_jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/chatboxes_jpg.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/logo/chatboxes_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/chatboxes_png.png -------------------------------------------------------------------------------- /src/student_network/static/images/logo/full_logo_jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/full_logo_jpg.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/logo/full_logo_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/full_logo_png.png -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Client Meeting (22nd Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Client Meeting (22nd Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Client Meeting (8th Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Client Meeting (8th Feb).docx -------------------------------------------------------------------------------- /docs/requirements-analysis/Analysis - Success Criteria.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/requirements-analysis/Analysis - Success Criteria.docx -------------------------------------------------------------------------------- /docs/requirements-analysis/Group N - Social Media Survey.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/requirements-analysis/Group N - Social Media Survey.pdf -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="student_network", 5 | package_dir={"": "src"}, 6 | packages=find_packages("src"), 7 | ) 8 | -------------------------------------------------------------------------------- /src/student_network/static/images/logo/text_stretch_jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/text_stretch_jpg.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/logo/text_stretch_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/text_stretch_png.png -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (12th Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (12th Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (15th Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (15th Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (19th Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (19th Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (21st Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (21st Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (22nd Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (22nd Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (24th Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (24th Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (26th Feb).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (26th Feb).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (3rd March).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (3rd March).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (5th March).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (5th March).docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Stand-Up Meeting (7th March).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Stand-Up Meeting (7th March).docx -------------------------------------------------------------------------------- /docs/requirements-analysis/Research - Back-End Solutions.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/requirements-analysis/Research - Back-End Solutions.docx -------------------------------------------------------------------------------- /docs/requirements-analysis/Research - Front-End Solutions.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/requirements-analysis/Research - Front-End Solutions.docx -------------------------------------------------------------------------------- /docs/meeting-notes/Minutes - Version Release Diagram Meeting.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/meeting-notes/Minutes - Version Release Diagram Meeting.docx -------------------------------------------------------------------------------- /src/student_network/static/images/logo/chatboxes_stretch_jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/chatboxes_stretch_jpg.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/logo/chatboxes_stretch_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/chatboxes_stretch_png.png -------------------------------------------------------------------------------- /src/student_network/static/images/logo/full_logo_stretch_jpg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/full_logo_stretch_jpg.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/logo/full_logo_stretch_png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/full_logo_stretch_png.png -------------------------------------------------------------------------------- /src/student_network/static/images/reconnect_tile_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/reconnect_tile_background.png -------------------------------------------------------------------------------- /prototypes/password_hash.py: -------------------------------------------------------------------------------- 1 | import bcrypt 2 | 3 | password = input("Enter password: ") 4 | hash_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) 5 | print(hash_password) 6 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/static/poll/images/background.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/designs/django_test/mysite/poll/static/poll/images/background.gif -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/static/poll/style.css: -------------------------------------------------------------------------------- 1 | li a { 2 | color: green; 3 | font-weight: bolder; 4 | } 5 | 6 | body { 7 | background: white url("images/background.gif") no-repeat; 8 | } 9 | -------------------------------------------------------------------------------- /docs/requirements-analysis/Plan - Design Thinking and MoSCoW Matrix.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/requirements-analysis/Plan - Design Thinking and MoSCoW Matrix.docx -------------------------------------------------------------------------------- /src/student_network/static/images/logo/chatboxes_stretch_png_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/logo/chatboxes_stretch_png_square.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | email_validator==1.1.3 2 | Flask==2.3.2 3 | Flask_SocketIO==5.1.1 4 | Werkzeug==3.0.0 5 | Pillow==9.3.0 6 | bcrypt==3.2.0 7 | pytest==6.2.5 8 | pytest-steps==1.8.0 9 | coverage==6.3.1 10 | -------------------------------------------------------------------------------- /docs/requirements-analysis/Social Media Survey - Group N (Responses) (1).xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/docs/requirements-analysis/Social Media Survey - Group N (Responses) (1).xlsx -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/20ce965d-68aa-488f-b2ee-159b5c8b339c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/20ce965d-68aa-488f-b2ee-159b5c8b339c.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/2a607ae9-d4ab-4c34-bd59-7560f338302a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/2a607ae9-d4ab-4c34-bd59-7560f338302a.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/3a84ceae-1643-4258-93ee-ce04be4a61a3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/3a84ceae-1643-4258-93ee-ce04be4a61a3.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/3b9ab10f-cd01-4c12-9dce-b0cf38281e11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/3b9ab10f-cd01-4c12-9dce-b0cf38281e11.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/475a1eeb-64f6-4577-ba0b-be4e5fea06fd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/475a1eeb-64f6-4577-ba0b-be4e5fea06fd.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/477256f5-ecfd-4441-b7e7-554163c47dc6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/477256f5-ecfd-4441-b7e7-554163c47dc6.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/48615484-731c-47e2-bcc6-185f64c3c239.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/48615484-731c-47e2-bcc6-185f64c3c239.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/49b44c64-65e3-4e8a-850c-04ce10ebc581.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/49b44c64-65e3-4e8a-850c-04ce10ebc581.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/5e8405fa-0940-44da-9908-e7e077c67977.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/5e8405fa-0940-44da-9908-e7e077c67977.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/656629aa-dc27-489d-9af2-51913758f32b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/656629aa-dc27-489d-9af2-51913758f32b.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/683dcfe2-e014-49b3-987f-3ade3f30305d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/683dcfe2-e014-49b3-987f-3ade3f30305d.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/8ae6f094-09c1-4d9a-b9fd-1ad12a8fcc5a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/8ae6f094-09c1-4d9a-b9fd-1ad12a8fcc5a.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/8d1468c8-411a-4cbc-b1e4-2062761c98ce.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/8d1468c8-411a-4cbc-b1e4-2062761c98ce.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/9068dbb4-7d17-44a2-9ccb-8b0d878c8c83.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/9068dbb4-7d17-44a2-9ccb-8b0d878c8c83.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/a7c9f14d-4de6-4735-9b16-13e1e3df0ca4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/a7c9f14d-4de6-4735-9b16-13e1e3df0ca4.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/b3849c93-9baf-4366-8f9a-cb3609444d41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/b3849c93-9baf-4366-8f9a-cb3609444d41.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/e050510d-b544-42ba-bcd0-d75a1089b7e2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/e050510d-b544-42ba-bcd0-d75a1089b7e2.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/avatars/ea76ef51-d05e-455a-8bca-1dc2a7c82a4a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/avatars/ea76ef51-d05e-455a-8bca-1dc2a7c82a4a.jpg -------------------------------------------------------------------------------- /src/student_network/static/FormatBody.js: -------------------------------------------------------------------------------- 1 | function FormatBody(body) { 2 | body = markdown.toHTML(body); 3 | return body.replace( 4 | /(@(\w+))/gi, 5 | `$1` 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/13ecaa90-d72f-4283-bb8e-726eafe79aa7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/13ecaa90-d72f-4283-bb8e-726eafe79aa7.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/33d7749f-6031-4a0a-b252-3d7a8ae64331.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/33d7749f-6031-4a0a-b252-3d7a8ae64331.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/54544cbe-a346-404a-b71e-80ab4f641f76.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/54544cbe-a346-404a-b71e-80ab4f641f76.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/70dc8871-e424-454e-85bc-ac02ac9f834f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/70dc8871-e424-454e-85bc-ac02ac9f834f.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/78afe457-39ba-4bed-b5cd-8e1fa8db227e.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/78afe457-39ba-4bed-b5cd-8e1fa8db227e.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/877a3c17-2be4-4b21-a2b5-58d824aac62f.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/877a3c17-2be4-4b21-a2b5-58d824aac62f.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/af2ecd76-c9c4-4e1f-8153-498f2abb5ecc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/af2ecd76-c9c4-4e1f-8153-498f2abb5ecc.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/b76ff2d3-a292-4270-99a4-d4801321d626.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/b76ff2d3-a292-4270-99a4-d4801321d626.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/ba4aa665-94c9-44e9-a171-c0f797ee77a4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/ba4aa665-94c9-44e9-a171-c0f797ee77a4.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/c818c247-0fec-4a21-b2ca-f99ee7a2c903.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/c818c247-0fec-4a21-b2ca-f99ee7a2c903.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/d8a2f51b-14b8-42cd-a461-a06c79ed4833.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/d8a2f51b-14b8-42cd-a461-a06c79ed4833.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/eb3469c7-5a5b-4187-9e5c-c39cc884cbfc.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/eb3469c7-5a5b-4187-9e5c-c39cc884cbfc.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/f0f068ef-c325-49f4-831f-3d5129e26788.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/f0f068ef-c325-49f4-831f-3d5129e26788.jpg -------------------------------------------------------------------------------- /src/student_network/static/images/post_imgs/f98c6507-7755-435a-90f1-ade8f618781c.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IsaacCheng9/student-network/HEAD/src/student_network/static/images/post_imgs/f98c6507-7755-435a-90f1-ade8f618781c.jpg -------------------------------------------------------------------------------- /src/student_network/static/searchbar.js: -------------------------------------------------------------------------------- 1 | var input = document.getElementById("search"); 2 | input.addEventListener("keyup", function (event) { 3 | if (event.keyCode === 13) { 4 | event.preventDefault(); 5 | location.href = "/profile/" + input.value; 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /prototypes/create_database.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | conn = sqlite3.connect("../db.sqlite3") 4 | c = conn.cursor() 5 | 6 | c.execute( 7 | "CREATE TABLE Accounts([username] text, [password] text, " 8 | "[email] text, [type] text)" 9 | ) 10 | 11 | conn.commit() 12 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/templates/poll/results.html: -------------------------------------------------------------------------------- 1 |

{{ question.question_text }}

2 | 3 | 11 | 12 | Vote again? 13 | -------------------------------------------------------------------------------- /tests/test_general.py: -------------------------------------------------------------------------------- 1 | import student_network.helpers.helper_general as helper_general 2 | 3 | 4 | def test_is_allowed_photo_file(): 5 | """ 6 | Tests that only certain file types are accepted for uploading photos. 7 | """ 8 | file_name = "test.jpg" 9 | assert helper_general.is_allowed_photo_file(file_name) is True 10 | invalid_file_name = "test.txt" 11 | assert helper_general.is_allowed_photo_file(invalid_file_name) is False 12 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/templates/poll/index.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | {% if latest_question_list %} 6 | 15 | {% else %} 16 |

No polls are available.

17 | {% endif %} 18 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/mysite/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for mysite project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/mysite/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for mysite project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/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", "mysite.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/student_network/templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Error{% endblock %} {% block content 11 | %} {% if message %} 12 |
13 | {% for item in message %} 14 |

{{ item }}

15 | {% endfor %} 16 |
17 | {% endif %} {% endblock %} 18 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/templates/poll/detail.html: -------------------------------------------------------------------------------- 1 |

{{ question.question_text }}

2 | 3 | {% if error_message %} 4 |

{{ error_message }}

5 | {% endif %} 6 | 7 |
8 | {% csrf_token %} {% for choice in question.choice_set.all %} 9 | 15 |
17 | {% endfor %} 18 | 19 |
20 | -------------------------------------------------------------------------------- /docs/designs/django_test/README.md: -------------------------------------------------------------------------------- 1 | To run this Django mock polling application, navigate to the file-path of the 2 | outer 'mysite' folder (where the 'manage.py' file resides) from within the 3 | command prompt. Then simply run the command 'python manage.py runserver' 4 | to initialise the page. 5 | Go to http://127.0.0.1:8000/poll/ or http://127.0.0.1:8000/admin/ on your 6 | browser to view the webpages. 7 | 8 | After creating this initial Django test, and researching the availability of 9 | Django on our chosen hosting service (AWS), we decided the lack of group 10 | knowledge of the syntax of Django as well as the restrictions on Django 11 | when paired with AWS made Flask a better choice for our backend web app 12 | development using Python. -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/models.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | 7 | class Question(models.Model): 8 | question_text = models.CharField(max_length=200) 9 | pub_date = models.DateTimeField("date published") 10 | 11 | def __str__(self): 12 | return self.question_text 13 | 14 | def was_published_recently(self): 15 | now = timezone.now() 16 | return now - datetime.timedelta(days=1) <= self.pub_date <= now 17 | 18 | 19 | class Choice(models.Model): 20 | question = models.ForeignKey(Question, on_delete=models.CASCADE) 21 | choice_text = models.CharField(max_length=200) 22 | votes = models.IntegerField(default=0) 23 | 24 | def __str__(self): 25 | return self.choice_text 26 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import views 4 | 5 | """ 6 | app_name = 'poll' 7 | urlpatterns = [ 8 | # ex: /polls/ 9 | path('', views.index, name='index'), 10 | # ex: /polls/5/ 11 | path('/', views.detail, name='detail'), 12 | # ex: /polls/5/results/ 13 | path('/results/', views.results, name='results'), 14 | # ex: /polls/5/vote/ 15 | path('/vote/', views.vote, name='vote'), 16 | ] 17 | """ 18 | 19 | 20 | app_name = "poll" 21 | urlpatterns = [ 22 | path("", views.IndexView.as_view(), name="index"), 23 | path("/", views.DetailView.as_view(), name="detail"), 24 | path("/results/", views.ResultsView.as_view(), name="results"), 25 | path("/vote/", views.vote, name="vote"), 26 | ] 27 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/mysite/urls.py: -------------------------------------------------------------------------------- 1 | """mysite URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import include, path 18 | 19 | urlpatterns = [ 20 | path("poll/", include("poll.urls")), 21 | path("admin/", admin.site.urls), 22 | ] 23 | -------------------------------------------------------------------------------- /docs/designs/database test/database-test.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, request 2 | 3 | import boto3 4 | import json 5 | 6 | 7 | class Comment: 8 | def __init__(self, username, comment): 9 | self.username = username 10 | self.comment = comment 11 | 12 | 13 | # client = boto3.client("dynamodb") 14 | db = boto3.resource("dynamodb", "us-east-2") 15 | 16 | app = Flask(__name__) 17 | 18 | 19 | @app.route("/", methods=["GET", "POST"]) 20 | def home(): 21 | return render_template("index.html", comments=ReadComments()) 22 | 23 | 24 | def ReadComments(): 25 | table = db.Table("comments") 26 | 27 | response = table.scan() 28 | 29 | items = response["Items"] 30 | 31 | comments = [] 32 | 33 | for item in items: 34 | newComment = Comment(item["username"], item["comment"]) 35 | comments.append(newComment) 36 | 37 | return comments 38 | 39 | 40 | ReadComments() 41 | -------------------------------------------------------------------------------- /utils/change_demo_account_passwords.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utility for changing password for all demo accounts in the database. This 3 | changes all passwords to the password you entered using the bcrypt encryption 4 | algorithm to ensure compatibility with the login system. 5 | """ 6 | 7 | import sqlite3 8 | import bcrypt 9 | 10 | password = input("Enter new password: ") 11 | usernames = [ 12 | "student1", 13 | "student2", 14 | "student3", 15 | "student4", 16 | "staffuser", 17 | "adminuser", 18 | "staffusertwo", 19 | "student5", 20 | "student6", 21 | "student7", 22 | "student8", 23 | "student1000", 24 | "student1001", 25 | "student1002", 26 | ] 27 | for username in usernames: 28 | with sqlite3.connect("db.sqlite3") as conn: 29 | cur = conn.cursor() 30 | hash_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()) 31 | cur.execute( 32 | "UPDATE ACCOUNTS SET password=? WHERE username=?;", 33 | ( 34 | hash_password, 35 | username, 36 | ), 37 | ) 38 | conn.commit() 39 | print("All account passwords have been successfully changed.") 40 | -------------------------------------------------------------------------------- /src/student_network/templates/quiz_results.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Quiz Results{% endblock %} {% block 11 | content %} 12 | 13 |
14 | {% for question in question_feedback %} {%if question[1] == question[2]%} 15 |
16 |
{{question[0]}}
17 |
{{question[2]}}
18 |
Correct
19 |
20 | {%endif%} {%if question[1] != question[2]%} 21 |
22 |
{{question[0]}}
23 |
Your answer: {{question[1]}}
24 |
Correct Answer: {{question[2]}}
25 |
26 | {%endif%} {%endfor%} 27 | 28 |

Score: {{score}} / {{question_feedback | length}} ({{percentage}}%)

29 | Back to Quiz Page 30 |
31 | {%endblock%} 32 | -------------------------------------------------------------------------------- /src/student_network/templates/admin.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block title %}Staff Account Requests{% endblock %} 2 | {% block content %} 3 |
4 |
5 | Staff Account Requests ({{requests|length}}) 6 |
7 |
8 | {% if requests|length > 0 %} {% for request in requests %} 9 |
10 |
11 |
15 | 19 |
20 |
21 |
25 | 29 |
30 |
31 |
32 | {{ request }} 35 |
36 |
37 | {% endfor %} {%else:%} 38 |

No Requests

39 | {%endif%} 40 |
41 |
42 | {%endblock%} 43 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | # Controls when the action will run. 4 | on: 5 | # Triggers the workflow on push events but only for the main branch. 6 | push: 7 | # Allows you to run this workflow manually from the Actions tab. 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | # The type of runner that the job will run on. 13 | runs-on: ubuntu-latest 14 | # Configures the build to use the latest version of Python 3. 15 | strategy: 16 | matrix: 17 | python-version: [3.x] 18 | 19 | steps: 20 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can 21 | # access it. 22 | - uses: actions/checkout@v2 23 | 24 | - name: Run Python code formatting with Black 25 | uses: lgeiger/black-action@v1.0.1 26 | with: 27 | args: "." 28 | 29 | - name: Switch to current branch 30 | run: git checkout ${{ env.BRANCH }} 31 | 32 | - name: Set up Python ${{ matrix.python-version }} 33 | uses: actions/setup-python@v1 34 | with: 35 | python-version: ${{ matrix.python-version }} 36 | 37 | - name: Install dependencies 38 | run: | 39 | python -m pip install --upgrade pip 40 | pip install -r requirements.txt 41 | pip install -e . 42 | 43 | - name: Run unit tests with Pytest 44 | run: coverage run --source=src -m pytest -v 45 | 46 | - name: Get code coverage report 47 | run: coverage report -m 48 | -------------------------------------------------------------------------------- /src/student_network/views/chat.py: -------------------------------------------------------------------------------- 1 | """ 2 | Handles the view for the chat system and related functionality. 3 | """ 4 | import os 5 | 6 | import student_network.helpers.helper_connections as helper_connections 7 | import student_network.helpers.helper_general as helper_general 8 | import student_network.helpers.helper_profile as helper_profile 9 | from flask import Blueprint, render_template 10 | from flask import session 11 | 12 | chat_blueprint = Blueprint( 13 | "chat", __name__, static_folder="static", template_folder="templates" 14 | ) 15 | 16 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 17 | db_path = os.path.join(BASE_DIR, "db.sqlite3") 18 | 19 | 20 | @chat_blueprint.route("/chat") 21 | def chat(): 22 | chat_rooms = helper_general.get_rooms() 23 | 24 | return render_template( 25 | "chat.html", 26 | requestCount=helper_connections.get_connection_request_count(), 27 | username=session["username"], 28 | rooms=chat_rooms, 29 | showChat=False, 30 | notifications=helper_general.get_notifications(), 31 | ) 32 | 33 | 34 | @chat_blueprint.route("/chat/") 35 | def chat_username(username): 36 | chat_rooms = helper_general.get_rooms() 37 | 38 | messages = helper_general.get_messages(username)[0] 39 | 40 | return render_template( 41 | "chat.html", 42 | requestCount=helper_connections.get_connection_request_count(), 43 | username=session["username"], 44 | rooms=chat_rooms, 45 | showChat=True, 46 | room=username, 47 | messages=messages, 48 | notifications=helper_general.get_notifications(), 49 | ) 50 | -------------------------------------------------------------------------------- /tests/test_social_profiles.py: -------------------------------------------------------------------------------- 1 | import student_network.helpers.helper_profile as helper_profile 2 | 3 | 4 | def test_invalid_edit_profile(): 5 | """ 6 | Tests that invalid profile editing details are rejected. 7 | """ 8 | bio = [ 9 | "bio", 10 | "bio", 11 | "bio", 12 | "biobiobiobiobiobiobiobiobiobiobiobiobiobiobio" 13 | "biobiobiobiobiobiobiobiobiobiobiobiobiobiobio" 14 | "biobiobiobiobiobiobiobiobiobiobiobiobiobiobio" 15 | "biobiobiobiobiobiobiobiobi", 16 | ] 17 | gender = ["Male", "Male", "bad", "Male"] 18 | dob = ["2001-05-31", "2001-05-31", "2001-05-31", "2001-05-31"] 19 | hobbies = [["hobby"], ["badhobbybadhobbybadhobbybadhobby"], ["hobby"], ["hobby"]] 20 | interests = [ 21 | ["badinterestbadinterestbadinterest"], 22 | ["interest"], 23 | ["interest"], 24 | ["interest"], 25 | ] 26 | for num in range(len(bio)): 27 | valid, _ = helper_profile.validate_edit_profile( 28 | bio[num], gender[num], dob[num], hobbies[num], interests[num] 29 | ) 30 | assert valid is False 31 | 32 | 33 | def test_valid_edit_profile(): 34 | """ 35 | Tests that valid profile editing details are accepted. 36 | """ 37 | valid, _ = helper_profile.validate_edit_profile( 38 | "good bio", "Male", "2001-01-31", ["hobby"], ["interest"] 39 | ) 40 | assert valid is True 41 | 42 | 43 | def test_null_edit_profile(): 44 | """ 45 | Tests that null profile editing details are accepted. 46 | """ 47 | valid, _ = helper_profile.validate_edit_profile("", "Male", "", [], []) 48 | assert valid is True 49 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.1.6 on 2021-02-03 15:54 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | initial = True 9 | 10 | dependencies = [] 11 | 12 | operations = [ 13 | migrations.CreateModel( 14 | name="Question", 15 | fields=[ 16 | ( 17 | "id", 18 | models.AutoField( 19 | auto_created=True, 20 | primary_key=True, 21 | serialize=False, 22 | verbose_name="ID", 23 | ), 24 | ), 25 | ("question_text", models.CharField(max_length=200)), 26 | ("pub_date", models.DateTimeField(verbose_name="date published")), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name="Choice", 31 | fields=[ 32 | ( 33 | "id", 34 | models.AutoField( 35 | auto_created=True, 36 | primary_key=True, 37 | serialize=False, 38 | verbose_name="ID", 39 | ), 40 | ), 41 | ("choice_text", models.CharField(max_length=200)), 42 | ("votes", models.IntegerField(default=0)), 43 | ( 44 | "question", 45 | models.ForeignKey( 46 | on_delete=django.db.models.deletion.CASCADE, to="poll.question" 47 | ), 48 | ), 49 | ], 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /src/student_network/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | {% extends "base.html" %} {% block title %}Login{% endblock %} {% block 12 | content%} 13 |
14 |
15 |
16 |
17 |
Login
18 |
19 |
20 | {% if 'login' in errors %} 21 |
22 |
Incorrect Credentials
23 |

Username and/or password is incorrect.

24 |
25 | {% endif %} 26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |

36 | Forgot your password? 37 |

38 |
39 |
40 |
41 |

42 | Don't have an account? 43 | Register here. 44 |

45 |
46 | 47 |
48 |
49 |
50 | {%endblock%} 51 | -------------------------------------------------------------------------------- /docs/designs/database test/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 16 | 17 | 18 | 19 |
20 |
21 |

Comments

22 | {%for comment in comments%} 23 |
24 | 25 | 26 | 27 |
28 | {{comment.username}} 29 | 32 |
{{comment.comment}}
33 |
34 | Reply 35 |
36 |
37 |
38 | {%endfor%} 39 |
40 | 41 |

Write a comment

42 | 43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 |
52 |
53 | 54 | 55 |
56 | 57 | -------------------------------------------------------------------------------- /src/student_network/templates/flashcards_set.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Flashcards view set{% endblock %} {% 11 | block content %} 12 | 13 | 14 | {% if errors %} 15 |
16 |
17 |
Error:
18 | {% for item in errors %} 19 |

{{ item }}

20 | {% endfor %} 21 |
22 |
23 | {% endif %} 24 | 25 |
26 |
27 |

{{set_name}}

28 |
29 |
30 | 31 | {% if questions %} {% for question in questions.keys() %} 32 |
33 |
34 |
35 |
Question: {{question}}
36 |
Answer: {{questions[question]}}
37 |
38 |
39 |
40 | {% endfor %} {% else %} 41 |
42 |
43 | 44 |
45 |
46 | {%endif%} 47 | 48 | 59 | 60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /docs/designs/Achievements table reference.csv: -------------------------------------------------------------------------------- 1 | Achievement ID,Achievement Name,Achievement Description,XP value,Associated icon,Rarity/icon colour 2 | 1,Look at you,Visit your profile page for the first time,25,address card,bronze 3 | 2,Looking good,Visit someone else's profile for the first time,25,eye,bronze 4 | 3,Show it off,Open the achievements tab for the first time,25,trophy,bronze 5 | 4,Connected,Add your first connection,50,address book,silver 6 | 5,Popular,Add 10 connections,75,address book,gold 7 | 6,Centre of attention,Add 100 connections,150,address book,platinum 8 | 7,Express yourself,Post to your social feed for the first time,50,file alternate,silver 9 | 8,5 posts,Post to your social feed 5 times,75,file alternate,gold 10 | 9,20 posts,Post to your social feed 20 times,150,file alternate,platinum 11 | 10,Commentary,Make your first comment on a post,50,comment,silver 12 | 11,Describe yourself,Describe yourself by writing a bio,50,tags,silver 13 | 12,Friends,Add your first close connection,50,users,silver 14 | 13,Friend group,Add 10 close connections,75,users,gold 15 | 14,Reaching out,Connect with 1 person outside of your subject,50,handshake,silver 16 | 15,Outside your bubble,Connect with 10 people outside of your subject,75,handshake,gold 17 | 16,Shared intrests,Connect with someone who has a mutual intrest,50,bowling ball,silver 18 | 17,Getting social,Send a connection request,50,user plus,silver 19 | 18,Show yourself,Add a profile picture,50,user circle,silver 20 | 19,Liking that,Like a post for the first time,50,thumbs up,silver 21 | 20,First like,Receive a like on a post,50,heart,silver 22 | 21,Hot topic,Have a post reach 10 comments,100,comments,gold 23 | 22,Everyone loves you,Have a post reach 50 likes,100,heart,gold 24 | 23,Secret meeting,Login during a special event,300,user secret,diamond 25 | 24,Show the love,Like 50 posts,75,thumbs up,gold 26 | 25,Loving everything,Like 500 posts,150,thumbs up,platinum 27 | 26,Shared hobbies,Connect with someone who has a mutual hobby,50,paint brush,silver 28 | -------------------------------------------------------------------------------- /src/student_network/static/hobbies.js: -------------------------------------------------------------------------------- 1 | hTags = []; 2 | hobbyInput = document.getElementsByName("hobbies_input")[0]; 3 | hValues = document.createElement("input"); 4 | hValues.setAttribute("type", "hidden"); 5 | hValues.setAttribute("name", "hobbies"); 6 | hobbyInput.appendChild(hValues); 7 | 8 | hobbyInput.addEventListener("input", function () { 9 | let enteredhTags = hobbyInput.value.split(","); 10 | if (enteredhTags.length > 1) { 11 | enteredhTags.forEach(function (t) { 12 | let filteredTag = filterHTag(t); 13 | if (filteredTag.length > 0) addHTag(filteredTag); 14 | }); 15 | hobbyInput.value = ""; 16 | } 17 | }); 18 | 19 | hobbyInput.addEventListener("keydown", function (e) { 20 | let keyCode = e.which || e.keyCode; 21 | if (keyCode === 8 && hobbyInput.value.length === 0 && hTags.length > 0) { 22 | removeHTag(hTags.length - 1); 23 | } 24 | }); 25 | 26 | function addHTag(text) { 27 | let tag = { 28 | text: text, 29 | element: document.createElement("div"), 30 | }; 31 | 32 | label = document.createElement("div"); 33 | label.classList.add("ui", "label"); 34 | label.textContent = tag.text; 35 | 36 | tag.element.classList.add("ui", "labels", "teal"); 37 | tag.element.appendChild(label); 38 | 39 | let closeBtn = document.createElement("i"); 40 | closeBtn.classList.add("close", "icon"); 41 | closeBtn.addEventListener("click", function () { 42 | removeHTag(hTags.indexOf(tag)); 43 | }); 44 | tag.element.firstElementChild.appendChild(closeBtn); 45 | 46 | hTags.push(tag); 47 | 48 | document.getElementsByName("hobby-div")[0].appendChild(tag.element); 49 | 50 | refreshHTags(); 51 | } 52 | 53 | function removeHTag(index) { 54 | let tag = hTags[index]; 55 | hTags.splice(index, 1); 56 | tag.element.remove(); 57 | refreshhTags(); 58 | } 59 | 60 | function refreshHTags() { 61 | let hTagsList = []; 62 | hTags.forEach(function (t) { 63 | hTagsList.push(t.text); 64 | }); 65 | hValues.value = hTagsList.join(","); 66 | } 67 | 68 | function filterHTag(tag) { 69 | return tag 70 | .replace(/[^\w -]/g, "") 71 | .trim() 72 | .replace(/\W+/g, "-"); 73 | } 74 | -------------------------------------------------------------------------------- /src/student_network/static/interests.js: -------------------------------------------------------------------------------- 1 | iTags = []; 2 | interestInput = document.getElementsByName("interests_input")[0]; 3 | iValues = document.createElement("input"); 4 | iValues.setAttribute("type", "hidden"); 5 | iValues.setAttribute("name", "interests"); 6 | interestInput.appendChild(iValues); 7 | 8 | interestInput.addEventListener("input", function () { 9 | let enterediTags = interestInput.value.split(","); 10 | if (enterediTags.length > 1) { 11 | enterediTags.forEach(function (t) { 12 | let filteredTag = filterITag(t); 13 | if (filteredTag.length > 0) addITag(filteredTag); 14 | }); 15 | interestInput.value = ""; 16 | } 17 | }); 18 | 19 | interestInput.addEventListener("keydown", function (e) { 20 | let keyCode = e.which || e.keyCode; 21 | if (keyCode === 8 && interestInput.value.length === 0 && iTags.length > 0) { 22 | removeITag(iTags.length - 1); 23 | } 24 | }); 25 | 26 | function addITag(text) { 27 | let tag = { 28 | text: text, 29 | element: document.createElement("div"), 30 | }; 31 | 32 | label = document.createElement("div"); 33 | label.classList.add("ui", "label"); 34 | label.textContent = tag.text; 35 | 36 | tag.element.classList.add("ui", "labels", "purple"); 37 | tag.element.appendChild(label); 38 | 39 | let closeBtn = document.createElement("i"); 40 | closeBtn.classList.add("close", "icon"); 41 | closeBtn.addEventListener("click", function () { 42 | removeITag(iTags.indexOf(tag)); 43 | }); 44 | tag.element.firstElementChild.appendChild(closeBtn); 45 | 46 | iTags.push(tag); 47 | 48 | document.getElementsByName("interest-div")[0].appendChild(tag.element); 49 | 50 | refreshITags(); 51 | } 52 | 53 | function removeITag(index) { 54 | let tag = iTags[index]; 55 | iTags.splice(index, 1); 56 | tag.element.remove(); 57 | refreshITags(); 58 | } 59 | 60 | function refreshITags() { 61 | let iTagsList = []; 62 | iTags.forEach(function (t) { 63 | iTagsList.push(t.text); 64 | }); 65 | iValues.value = iTagsList.join(","); 66 | } 67 | 68 | function filterITag(tag) { 69 | return tag 70 | .replace(/[^\w -]/g, "") 71 | .trim() 72 | .replace(/\W+/g, "-"); 73 | } 74 | -------------------------------------------------------------------------------- /src/student_network/templates/leaderboard.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% extends "base.html" %} 5 | {% block title %}Leaderboard{% endblock %} 6 | {% block content %} 7 | {% if errors %} 8 |
9 |
Error:
10 | {% for item in errors %} 11 |

{{ item }}

12 | {% endfor %} 13 |
14 | {% endif %} 15 |
16 |
17 |
18 |
Your Ranking
19 |
#{{ myRanking }} / {{ totalUserCount }} 21 |
22 | Top {{percent}}% 23 |
24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {% for i in range(leaderboard|length) %} 37 | 38 | 39 | 49 | 50 | 51 | 52 | {% endfor %} 53 | 54 |

Rank

User

Level

Total XP

#{{ i + 1}}

40 |

41 | 42 |
43 | {{ leaderboard[i][0] }} 44 |
{{ leaderboard[i][4] }} 45 |
46 |
47 |

48 |

{{ leaderboard[i][3][0] }}

{{ leaderboard[i][1] }}

55 |
56 | {% endblock %} 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Student Network 2 | 3 | ## Reporting Issues 4 | 5 | We use 6 | [GitHub Projects as a Kanban board](https://github.com/IsaacCheng9/student-network/projects/1) 7 | and integrate the GitHub Issues feature to track the progress of tasks and 8 | related pull requests - remember to link the issue to the Kanban board. 9 | 10 | Include the following information when reporting an issue (where possible): 11 | 12 | - Breakdown of the problem 13 | - Expected changes 14 | - Reproducible steps (if reporting a bug) 15 | 16 | The `high priority` label should be sparingly to avoid diluting its 17 | significance - overuse of this defeats the purpose of the label. 18 | 19 | ## Project Structure 20 | 21 | This project uses _Flask Blueprints_ to organise the application (more info 22 | [here](https://exploreflask.com/en/latest/blueprints.html)). 23 | 24 | We use a _functional structure_, where pieces of the application are organised 25 | by what they do (further explanation using Facebook as an example 26 | [here](https://exploreflask.com/en/latest/blueprints.html#which-one-is-best)). 27 | Each Python file in the [/views/](src/student_network/views) directory represents a 28 | blueprint, which is registered in the main application in 29 | [app.py](src/student_network/app.py). 30 | 31 | ## Pull Requests 32 | 33 | Include the following information when submitting a pull request: 34 | 35 | - Overview of the changes 36 | - Where needed, add explanations about what the changes will effect and why 37 | they were made. 38 | - Related issues 39 | - Using the `This fixes {link to issue}` syntax will automatically close the 40 | issue once merged. 41 | 42 | ## Unit Tests 43 | 44 | Unit tests should be created and maintained as changes are made to the core 45 | functionality to improve maintainability. 46 | 47 | We use the _Pytest_ framework for this, which has been integrated into GitHub 48 | Actions for automated unit testing (see below). 49 | 50 | ## GitHub Actions 51 | 52 | After pushing to the repository, the workflow in GitHub Actions consists of: 53 | 54 | - Running Python code formatting with _Black_ 55 | - This ensures good readability and a consistent style across the codebase to 56 | reduce diffs for code reviews. 57 | - Running all unit tests for Python with _Pytest_ 58 | - This helps prevent runtime errors in production. 59 | - Test should be created and kept updated to facilitate this. 60 | -------------------------------------------------------------------------------- /src/student_network/static/slideshow.js: -------------------------------------------------------------------------------- 1 | class Slideshow { 2 | constructor(targetID, images, captions) { 3 | this.images = images; 4 | 5 | this.targetID = targetID; 6 | 7 | this.index = 0; 8 | 9 | var html = `
`; 10 | 11 | for (var i = 0; i < images.length; i++) { 12 | html += `
13 |
${i + 1} / ${ 14 | images.length 15 | }
16 | 19 |
${captions[i]}
20 |
`; 21 | } 22 | 23 | html += ` 24 | `; 25 | 26 | html += `
27 |
`; 28 | 29 | for (var i = 0; i < images.length; i++) { 30 | html += ``; 31 | } 32 | 33 | $("#" + targetID).append(html + "
"); 34 | 35 | var me = this; 36 | 37 | var id = "#" + this.targetID; 38 | 39 | $(id) 40 | .find(".dot") 41 | .on("click", function () { 42 | me.SetIndex($(this)); 43 | }); 44 | 45 | $(id) 46 | .find(".prev") 47 | .on("click", function () { 48 | me.PrevSlide(); 49 | }); 50 | 51 | $(id) 52 | .find(".next") 53 | .on("click", function () { 54 | me.NextSlide(); 55 | }); 56 | 57 | this.UpdateUI(); 58 | } 59 | 60 | NextSlide() { 61 | this.index = (this.index + 1) % this.images.length; 62 | this.UpdateUI(); 63 | } 64 | 65 | PrevSlide() { 66 | this.index = (this.index - 1) % this.images.length; 67 | this.UpdateUI(); 68 | } 69 | 70 | SetIndex(elem) { 71 | var index = elem.index(); 72 | 73 | this.index = index; 74 | this.UpdateUI(); 75 | } 76 | 77 | UpdateUI() { 78 | var slides = $("#" + this.targetID).find(".slide-img"); 79 | var dots = $("#" + this.targetID).find(".dot"); 80 | 81 | $(slides).css("display", "none"); 82 | $(dots).removeClass("active"); 83 | 84 | $(slides).eq(this.index).css("display", "block"); 85 | $(dots).eq(this.index).addClass("active"); 86 | } 87 | } 88 | 89 | function OpenModal(imgSrc) { 90 | $(".modal img").attr("src", imgSrc); 91 | $(".modal").fadeIn(); 92 | } 93 | 94 | function CloseModal() { 95 | $(".modal").fadeOut(); 96 | } 97 | 98 | $(".modal").hide(); 99 | -------------------------------------------------------------------------------- /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # pipenv 86 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 87 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 88 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 89 | # install all needed dependencies. 90 | #Pipfile.lock 91 | 92 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 93 | __pypackages__/ 94 | 95 | # Celery stuff 96 | celerybeat-schedule 97 | celerybeat.pid 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Environments 103 | .env 104 | .venv 105 | env/ 106 | venv/ 107 | ENV/ 108 | env.bak/ 109 | venv.bak/ 110 | 111 | # Spyder project settings 112 | .spyderproject 113 | .spyproject 114 | 115 | # Rope project settings 116 | .ropeproject 117 | 118 | # mkdocs documentation 119 | /site 120 | 121 | # mypy 122 | .mypy_cache/ 123 | .dmypy.json 124 | dmypy.json 125 | 126 | # Pyre type checker 127 | .pyre/ 128 | 129 | # VS Code 130 | .vscode/launch.json 131 | .vscode/settings.json 132 | 133 | # IntelliJ 134 | .idea 135 | identifier.sqlite 136 | 137 | # Databases 138 | *.db -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '43 16 * * 2' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript', 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /src/student_network/static/styles/mobile.css: -------------------------------------------------------------------------------- 1 | /* Semantic UI has these classes, however they're only applicable to*/ 2 | /* grids, containers, rows and columns.*/ 3 | /* plus, there isn't any `mobile hidden`, `X hidden` class.*/ 4 | /* this snippet is using the same class names and same approach*/ 5 | /* plus a bit more but to all elements.*/ 6 | /* see https://github.com/Semantic-Org/Semantic-UI/issues/1114*/ 7 | 8 | /* Mobile */ 9 | @media only screen and (max-width: 767px) { 10 | [class*="mobile hidden"], 11 | [class*="tablet only"]:not(.mobile), 12 | [class*="computer only"]:not(.mobile), 13 | [class*="large screen only"]:not(.mobile), 14 | [class*="widescreen only"]:not(.mobile), 15 | [class*="or lower hidden"] { 16 | display: none !important; 17 | } 18 | } 19 | 20 | /* Tablet / iPad Portrait */ 21 | @media only screen and (min-width: 768px) and (max-width: 991px) { 22 | [class*="mobile only"]:not(.tablet), 23 | [class*="tablet hidden"], 24 | [class*="computer only"]:not(.tablet), 25 | [class*="large screen only"]:not(.tablet), 26 | [class*="widescreen only"]:not(.tablet), 27 | [class*="or lower hidden"]:not(.mobile) { 28 | display: none !important; 29 | } 30 | } 31 | 32 | /* Computer / Desktop / iPad Landscape */ 33 | @media only screen and (min-width: 992px) and (max-width: 1199px) { 34 | [class*="mobile only"]:not(.computer), 35 | [class*="tablet only"]:not(.computer), 36 | [class*="computer hidden"], 37 | [class*="large screen only"]:not(.computer), 38 | [class*="widescreen only"]:not(.computer), 39 | [class*="or lower hidden"]:not(.tablet):not(.mobile) { 40 | display: none !important; 41 | } 42 | } 43 | 44 | /* Large Monitor */ 45 | @media only screen and (min-width: 1200px) and (max-width: 1919px) { 46 | [class*="mobile only"]:not([class*="large screen"]), 47 | [class*="tablet only"]:not([class*="large screen"]), 48 | [class*="computer only"]:not([class*="large screen"]), 49 | [class*="large screen hidden"], 50 | [class*="widescreen only"]:not([class*="large screen"]), 51 | [class*="or lower hidden"]:not(.computer):not(.tablet):not(.mobile) { 52 | display: none !important; 53 | } 54 | } 55 | 56 | /* Widescreen Monitor */ 57 | @media only screen and (min-width: 1920px) { 58 | [class*="mobile only"]:not([class*="widescreen"]), 59 | [class*="tablet only"]:not([class*="widescreen"]), 60 | [class*="computer only"]:not([class*="widescreen"]), 61 | [class*="large screen only"]:not([class*="widescreen"]), 62 | [class*="widescreen hidden"], 63 | [class*="widescreen or lower hidden"] { 64 | display: none !important; 65 | } 66 | } -------------------------------------------------------------------------------- /prototypes/registration.py: -------------------------------------------------------------------------------- 1 | """Handles the registration process for a new user account.""" 2 | import re 3 | import sqlite3 4 | from email_validator import validate_email, EmailNotValidError 5 | 6 | 7 | def main(): 8 | with sqlite3.connect("../db.sqlite3") as conn: 9 | cur = conn.cursor() 10 | # Gets the user inputs from the registration page. 11 | username = "ic324" 12 | password = "Password01" 13 | password_confirm = "Password01" 14 | email = "ic324@exeter.ac.uk" 15 | valid = validate_registration(cur, username, password, password_confirm, email) 16 | print(valid) 17 | conn.commit() 18 | 19 | 20 | def validate_registration( 21 | cur, username: str, password: str, password_confirm: str, email: str 22 | ) -> bool: 23 | """ 24 | Validates the registration details to ensure that the email address is 25 | valid, and that the passwords in the form match. 26 | 27 | Arguments: 28 | cur: Cursor for the SQLite database. 29 | username: The username input by the user in the form. 30 | password: The password input by the user in the form. 31 | password_confirm: The password confirmation input by the user in the 32 | form. 33 | email: The email address input by the user in the form. 34 | 35 | Returns: 36 | valid (bool): States whether the registration details are valid. 37 | """ 38 | valid = True 39 | 40 | # Checks that the email address has the correct format, checks whether it 41 | # exists, and isn't a blacklist email. 42 | try: 43 | valid_email = validate_email(email) 44 | # Updates with the normalised form of the email address. 45 | email = valid_email.email 46 | except EmailNotValidError: 47 | print("Email is invalid!") 48 | valid = False 49 | 50 | # Checks that the email address has the University of Exeter domain. 51 | domain = re.search("@.*", email).group() 52 | if domain != "@exeter.ac.uk": 53 | print("Email address does not belong to University of Exeter!") 54 | valid = False 55 | 56 | # Checks that the username hasn't already been registered. 57 | cur.execute("SELECT * FROM Accounts WHERE username=?;", (username,)) 58 | if cur.fetchone() is not None: 59 | print("Username has already been registered!") 60 | valid = False 61 | 62 | # Checks that the password has a minimum length of 6 characters, and at 63 | # least one number. 64 | if len(password) <= 5 or any(char.isdigit() for char in password) is False: 65 | print("Password does not meet requirements!") 66 | valid = False 67 | 68 | # Checks that the passwords match. 69 | if password != password_confirm: 70 | print("Passwords do not match!") 71 | valid = False 72 | 73 | return valid 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /src/student_network/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | A student network application which is presented as a web application using 3 | the Flask module. Students each have their own profile page, and they can post 4 | on their feed. 5 | """ 6 | import os 7 | import sqlite3 8 | from datetime import datetime 9 | 10 | import student_network.views.achievements as achievements 11 | import student_network.views.chat as chat 12 | import student_network.views.connections as connections 13 | import student_network.views.flashcards as flashcards 14 | import student_network.views.login as login 15 | import student_network.views.posts as posts 16 | import student_network.views.profile as profile 17 | import student_network.views.quizzes as quizzes 18 | import student_network.views.staff as staff 19 | from flask import Flask, request, session 20 | from flask_socketio import SocketIO 21 | 22 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 23 | DB_PATH = os.path.join(BASE_DIR, "db.sqlite3") 24 | 25 | app = Flask(__name__) 26 | socketio = SocketIO(app) 27 | app.register_blueprint(achievements.achievements_blueprint, url_prefix="") 28 | app.register_blueprint(chat.chat_blueprint, url_prefix="") 29 | app.register_blueprint(connections.connections_blueprint, url_prefix="") 30 | app.register_blueprint(login.login_blueprint, url_prefix="") 31 | app.register_blueprint(posts.posts_blueprint, url_prefix="") 32 | app.register_blueprint(profile.profile_blueprint, url_prefix="") 33 | app.register_blueprint(quizzes.quizzes_blueprint, url_prefix="") 34 | app.register_blueprint(flashcards.flashcards_blueprint, url_prefix="") 35 | app.register_blueprint(staff.staff_blueprint, url_prefix="") 36 | app.secret_key = ( 37 | '\xfd{H\xe5 <\x95\xf9\xe3\x96.5\xd1\x01O .content { 120 | position: absolute; 121 | top: 0; 122 | bottom: 0; 123 | width: 50%; 124 | left: 50%; 125 | transform: translate(-50%); 126 | } 127 | 128 | .modal .content img { 129 | position: relative; 130 | top: 0; 131 | bottom: 0; 132 | } 133 | 134 | .close-icon { 135 | font-size: 2em; 136 | cursor: pointer; 137 | color: #333; 138 | width: 100%; 139 | position: relative; 140 | top: 1em; 141 | right: 0; 142 | } 143 | -------------------------------------------------------------------------------- /src/student_network/views/staff.py: -------------------------------------------------------------------------------- 1 | """ 2 | Handles the view for staff administration tools and related functionality. 3 | """ 4 | 5 | import sqlite3 6 | 7 | import student_network.helpers.helper_connections as helper_connections 8 | from flask import Blueprint, redirect, render_template, session 9 | 10 | staff_blueprint = Blueprint( 11 | "staff", __name__, static_folder="static", template_folder="templates" 12 | ) 13 | 14 | 15 | @staff_blueprint.route("/admin", methods=["GET", "POST"]) 16 | def show_staff_requests() -> object: 17 | """ 18 | Displays requests to sign up as a staff member. 19 | 20 | Returns: 21 | The web page for handling administration. 22 | """ 23 | if "admin" in session: 24 | if not session["admin"]: 25 | return render_template( 26 | "error.html", 27 | message=["You are not logged in to an admin account"], 28 | requestCount=helper_connections.get_connection_request_count(), 29 | ) 30 | with sqlite3.connect("db.sqlite3") as conn: 31 | # Loads the list of connection requests and their avatars. 32 | requests = [] 33 | cur = conn.cursor() 34 | # Extracts incoming requests. 35 | cur.execute("SELECT username FROM ACCOUNTS WHERE type='pending_staff';") 36 | conn.commit() 37 | row = cur.fetchall() 38 | if len(row) > 0: 39 | for elem in row: 40 | requests.append(elem[0]) 41 | 42 | return render_template( 43 | "admin.html", 44 | requests=requests, 45 | requestCount=helper_connections.get_connection_request_count(), 46 | ) 47 | else: 48 | return render_template( 49 | "error.html", 50 | message=["You are not logged in to an admin account"], 51 | requestCount=helper_connections.get_connection_request_count(), 52 | ) 53 | 54 | 55 | @staff_blueprint.route("/accept_staff/", methods=["GET", "POST"]) 56 | def accept_staff(username: str): 57 | """ 58 | Accepts user as a staff member. 59 | 60 | Args: 61 | username: The user to accept as a staff member. 62 | 63 | Returns: 64 | Redirection to the administration page. 65 | """ 66 | with sqlite3.connect("db.sqlite3") as conn: 67 | cur = conn.cursor() 68 | cur.execute( 69 | "UPDATE ACCOUNTS SET type=? WHERE username=? ;", ("staff", username) 70 | ) 71 | return redirect("/admin") 72 | 73 | 74 | @staff_blueprint.route("/reject_staff/", methods=["GET", "POST"]) 75 | def reject_staff(username: str): 76 | """ 77 | Rejects user as staff member. 78 | 79 | Args: 80 | username: The user to reject as a staff member. 81 | 82 | Returns: 83 | Redirection to the administration page. 84 | """ 85 | with sqlite3.connect("db.sqlite3") as conn: 86 | cur = conn.cursor() 87 | cur.execute( 88 | "UPDATE ACCOUNTS SET type=? WHERE username=? ;", ("student", username) 89 | ) 90 | return redirect("/admin") 91 | -------------------------------------------------------------------------------- /src/student_network/templates/home_page.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Home Page{% endblock %} {% block 11 | content %} 12 |
13 |
14 |
15 |

Welcome to Reconnect!

16 |
17 |

18 | Reconnect is a student network application for the University of 19 | Exeter. 20 |

21 |
22 |
23 |
24 |
25 | 26 | Our aim with Reconnect is to increase student interaction with other 27 | students through fun games, posts, and discussions. This application will 28 | also encourage students to communicate and bond through degree-related 29 | topics, with the help of both student and staff-made quizzes. 30 |
31 | 32 | 33 | 34 |

Get Started:

35 | 36 | 37 | 38 | or 39 | 40 | 41 | 42 |

43 |
44 |

Search For a User:

45 |
46 |
47 | 52 | 53 |
54 |
55 | 56 |
57 |
58 |
59 | 93 | {% endblock %} 94 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponseRedirect 2 | from django.shortcuts import get_object_or_404, render 3 | from django.urls import reverse 4 | from django.views import generic 5 | 6 | from django.utils import timezone 7 | 8 | from .models import Choice, Question 9 | 10 | """ 11 | def index(request): 12 | return HttpResponse("Hello, world. You're at the poll index.") 13 | 14 | def detail(request, question_id): 15 | return HttpResponse("You're looking at question %s." % question_id) 16 | 17 | def results(request, question_id): 18 | question = get_object_or_404(Question, pk=question_id) 19 | return render(request, 'poll/results.html', {'question': question}) 20 | """ 21 | 22 | 23 | class IndexView(generic.ListView): 24 | template_name = "poll/index.html" 25 | context_object_name = "latest_question_list" 26 | 27 | def get_queryset(self): 28 | """ 29 | Return the last five published questions (not including those set to be 30 | published in the future). 31 | """ 32 | return Question.objects.filter(pub_date__lte=timezone.now()).order_by( 33 | "-pub_date" 34 | )[:5] 35 | 36 | 37 | class DetailView(generic.DetailView): 38 | model = Question 39 | template_name = "poll/detail.html" 40 | 41 | def get_queryset(self): 42 | """ 43 | Excludes any questions that aren't published yet. 44 | """ 45 | return Question.objects.filter(pub_date__lte=timezone.now()) 46 | 47 | 48 | class ResultsView(generic.DetailView): 49 | model = Question 50 | template_name = "poll/results.html" 51 | 52 | 53 | def vote(request, question_id): 54 | question = get_object_or_404(Question, pk=question_id) 55 | try: 56 | selected_choice = question.choice_set.get(pk=request.POST["choice"]) 57 | except (KeyError, Choice.DoesNotExist): 58 | # Redisplay the question voting form. 59 | return render( 60 | request, 61 | "poll/detail.html", 62 | { 63 | "question": question, 64 | "error_message": "You didn't select a choice.", 65 | }, 66 | ) 67 | else: 68 | selected_choice.votes += 1 69 | selected_choice.save() 70 | # Always return an HttpResponseRedirect after successfully dealing 71 | # with POST data. This prevents data from being posted twice if a 72 | # user hits the Back button. 73 | return HttpResponseRedirect(reverse("poll:results", args=(question.id,))) 74 | 75 | 76 | def index(request): 77 | latest_question_list = Question.objects.order_by("-pub_date")[:5] 78 | context = {"latest_question_list": latest_question_list} 79 | return render(request, "poll/index.html", context) 80 | 81 | 82 | def detail(request, question_id): 83 | try: 84 | question = Question.objects.get(pk=question_id) 85 | except Question.DoesNotExist: 86 | raise Http404("Question does not exist") 87 | return render(request, "poll/detail.html", {"question": question}) 88 | 89 | 90 | """ 91 | def detail(request, question_id): 92 | question = get_object_or_404(Question, pk=question_id) 93 | return render(request, 'poll/detail.html', {'question': question}) 94 | """ 95 | -------------------------------------------------------------------------------- /src/student_network/templates/flashcards_view.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Flashcard set list{% endblock %} {% 11 | block content %} 12 | 13 | 14 |
15 | {% if errors %} {% if "delete set" in errors %} 16 |
17 |
Success
18 |

Deck deleted successfully

19 |
20 | {% else %} 21 |
22 |
Error:
23 | {% for item in errors %} 24 |

{{ item }}

25 | {% endfor %} 26 |
27 | {% endif %} {% endif %} 28 | 59 |
60 | 61 | {% for card_set in sets %} 62 | 99 | {% endfor %} {% endblock %} 100 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/mysite/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for mysite project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.1.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.1/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "w_4$(rsh7hcg_a27w+5ua_k&d8$q&qg5okk++q0hj#qx*b+(e@" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | "poll.apps.PollConfig", 35 | "django.contrib.admin", 36 | "django.contrib.auth", 37 | "django.contrib.contenttypes", 38 | "django.contrib.sessions", 39 | "django.contrib.messages", 40 | "django.contrib.staticfiles", 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | "django.middleware.security.SecurityMiddleware", 45 | "django.contrib.sessions.middleware.SessionMiddleware", 46 | "django.middleware.common.CommonMiddleware", 47 | "django.middleware.csrf.CsrfViewMiddleware", 48 | "django.contrib.auth.middleware.AuthenticationMiddleware", 49 | "django.contrib.messages.middleware.MessageMiddleware", 50 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 51 | ] 52 | 53 | ROOT_URLCONF = "mysite.urls" 54 | 55 | TEMPLATES = [ 56 | { 57 | "BACKEND": "django.template.backends.django.DjangoTemplates", 58 | "DIRS": [], 59 | "APP_DIRS": True, 60 | "OPTIONS": { 61 | "context_processors": [ 62 | "django.template.context_processors.debug", 63 | "django.template.context_processors.request", 64 | "django.contrib.auth.context_processors.auth", 65 | "django.contrib.messages.context_processors.messages", 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = "mysite.wsgi.application" 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/3.1/ref/settings/#databases 76 | 77 | DATABASES = { 78 | "default": { 79 | "ENGINE": "django.db.backends.sqlite3", 80 | "NAME": BASE_DIR / "db.sqlite3", 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 91 | }, 92 | { 93 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/3.1/topics/i18n/ 106 | 107 | LANGUAGE_CODE = "en-us" 108 | 109 | TIME_ZONE = "GMT" 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/3.1/howto/static-files/ 120 | 121 | STATIC_URL = "/static/" 122 | -------------------------------------------------------------------------------- /src/student_network/templates/flashcards_play.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | 11 | 60 | 61 | {% extends "base.html" %} {% block title %} Flashcards {% endblock %} {% block 62 | content %} {% if question_count > 0 %} 63 | 64 | 65 | 66 | 67 | 68 |
69 | 70 |
71 |
72 | 75 | 78 | 81 |
82 | {% else %} 83 |
84 |
85 | 86 |
87 |
88 | {% endif %} 89 | 90 | 141 | 142 | {%endblock%} 143 | -------------------------------------------------------------------------------- /src/student_network/templates/privacy_policy.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Privacy Policy{% endblock %} {% block 11 | content %} 12 |
13 |
14 |
15 |
16 |
Privacy Policy
17 |
18 |
19 |
20 | 22 |

Reconnect Privacy Policy

23 | Please read this Privacy Policy carefully before agreeing to the 24 | Reconnect terms and conditions. Reconnect is operated by Exeter 25 | University Group Software Engineering Project Group N. The Reconnect 26 | team is a group of university computer science students with the goal 27 | of working alongside the Exeter University faculty, and more 28 | importantly the student body, to develop a brand new social media 29 | connecting students through these isolating times. We wont charge 30 | anyone for the use of Reconnect. 31 |

What Sort of Data Do We Collect?

32 |

Content You Generate

33 | We collect content, communications, usage, and other information you 34 | provide when you use the Reconnect network application. This data is 35 | stored for the purposes of usage analysis, demographic analysis, and 36 | recommendations such as relevant content. 37 |

Network and Connections

38 | We collect information about the people you are ‘connected’ with or 39 | ‘close friends’ with and how you interact with them. This information 40 | may used for the purpose of statistics such as mutual friends, and 41 | relevant connection requests. 42 |

Information and Opinions You Provide

43 | Occasionally, we may provide you with optional surveys asking for your 44 | opinion on certain parts of the Reconnect platform. This data is 45 | stored for us to ascertain general user opinions on our application 46 | and hopefully amend it to fix any issues or parts of the platform 47 | users seem unhappy with. 48 |

Promoting Safety, Integrity, and Security

49 | We use the information we collect to verify authenticity of content 50 | shared from accounts and promote safety on the Reconnect platform. 51 |

Protecting Your Data

52 | The Reconnect team wants to support you in protecting your data by 53 | helping you understand your rights according to the General Data 54 | Protection Regulation (GDPR). The GDPR states seven main principles 55 | for the lawful processing of personal data as follows: 56 |
    57 |
  • Lawfulness, Fairness and Transparency
  • 58 |
  • Purpose Limitation
  • 59 |
  • Data Minimisation
  • 60 |
  • Accuracy
  • 61 |
  • Storage Limitation
  • 62 |
  • Integrity and Confidentiality (Security)
  • 63 |
  • Accountability
  • 64 |
65 | If you have any concerns that your data associated with any 66 | (Reconnect) product is not being processed legally and securely 67 | according to these data protection laws, please contact our legal team 68 | at bg354@exeter.ac.uk. 69 |
70 | 76 |
77 |
78 |
79 |
80 | {%endblock%} 81 | -------------------------------------------------------------------------------- /src/student_network/templates/flashcards_edit.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Flashcards edit set{% endblock %} {% 11 | block content %} 12 | 13 | 14 | {% if errors %} {% if "save" in errors %} 15 |
16 |
Success
17 |

Deck saved successfully

18 |
19 | {% else %} 20 |
21 |
22 |
Error:
23 | {% for item in errors %} 24 |

{{ item }}

25 | {% endfor %} 26 |
27 |
28 | {% endif %} {% endif %} 29 | 30 |
35 |
36 |
37 |
38 | 39 | 46 |
47 |
48 |
49 | 50 | {% if questions %} {% for question in questions.keys() %} 51 |
52 |
53 |
54 |
55 |
56 | 63 |
64 |
65 |
66 |
67 | 74 |
75 |
76 |
77 | 85 |
86 |
87 |
88 |
89 |
90 | {% endfor %} {% else %} 91 |
92 |
93 | 94 |
95 |
96 | {%endif%} 97 | 98 | 99 | 103 | 104 | 105 | 109 | 110 | 111 | 115 | 116 | 125 | 126 | 127 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | {% endblock %} 138 | -------------------------------------------------------------------------------- /docs/designs/django_test/mysite/poll/tests.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | 3 | from django.test import TestCase 4 | from django.utils import timezone 5 | 6 | from django.urls import reverse 7 | 8 | from .models import Question 9 | 10 | 11 | class QuestionModelTests(TestCase): 12 | def test_was_published_recently_with_future_question(self): 13 | """ 14 | was_published_recently() returns False for questions whose pub_date 15 | is in the future. 16 | """ 17 | time = timezone.now() + datetime.timedelta(days=30) 18 | future_question = Question(pub_date=time) 19 | self.assertIs(future_question.was_published_recently(), False) 20 | 21 | def test_was_published_recently_with_old_question(self): 22 | """ 23 | was_published_recently() returns False for questions whose pub_date 24 | is older than 1 day. 25 | """ 26 | time = timezone.now() - datetime.timedelta(days=1, seconds=1) 27 | old_question = Question(pub_date=time) 28 | self.assertIs(old_question.was_published_recently(), False) 29 | 30 | def test_was_published_recently_with_recent_question(self): 31 | """ 32 | was_published_recently() returns True for questions whose pub_date 33 | is within the last day. 34 | """ 35 | time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) 36 | recent_question = Question(pub_date=time) 37 | self.assertIs(recent_question.was_published_recently(), True) 38 | 39 | def create_question(question_text, days): 40 | """ 41 | Create a question with the given `question_text` and published the 42 | given number of `days` offset to now (negative for questions published 43 | in the past, positive for questions that have yet to be published). 44 | """ 45 | time = timezone.now() + datetime.timedelta(days=days) 46 | return Question.objects.create(question_text=question_text, pub_date=time) 47 | 48 | 49 | class QuestionIndexViewTests(TestCase): 50 | def test_no_questions(self): 51 | """ 52 | If no questions exist, an appropriate message is displayed. 53 | """ 54 | response = self.client.get(reverse("poll:index")) 55 | self.assertEqual(response.status_code, 200) 56 | self.assertContains(response, "No polls are available.") 57 | self.assertQuerysetEqual(response.context["latest_question_list"], []) 58 | 59 | def test_past_question(self): 60 | """ 61 | Questions with a pub_date in the past are displayed on the 62 | index page. 63 | """ 64 | create_question(question_text="Past question.", days=-30) 65 | response = self.client.get(reverse("poll:index")) 66 | self.assertQuerysetEqual( 67 | response.context["latest_question_list"], [""] 68 | ) 69 | 70 | def test_future_question(self): 71 | """ 72 | Questions with a pub_date in the future aren't displayed on 73 | the index page. 74 | """ 75 | create_question(question_text="Future question.", days=30) 76 | response = self.client.get(reverse("poll:index")) 77 | self.assertContains(response, "No polls are available.") 78 | self.assertQuerysetEqual(response.context["latest_question_list"], []) 79 | 80 | def test_future_question_and_past_question(self): 81 | """ 82 | Even if both past and future questions exist, only past questions 83 | are displayed. 84 | """ 85 | create_question(question_text="Past question.", days=-30) 86 | create_question(question_text="Future question.", days=30) 87 | response = self.client.get(reverse("poll:index")) 88 | self.assertQuerysetEqual( 89 | response.context["latest_question_list"], [""] 90 | ) 91 | 92 | def test_two_past_questions(self): 93 | """ 94 | The questions index page may display multiple questions. 95 | """ 96 | create_question(question_text="Past question 1.", days=-30) 97 | create_question(question_text="Past question 2.", days=-5) 98 | response = self.client.get(reverse("poll:index")) 99 | self.assertQuerysetEqual( 100 | response.context["latest_question_list"], 101 | ["", ""], 102 | ) 103 | -------------------------------------------------------------------------------- /src/student_network/templates/members.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Members{% endblock %} {% block 11 | content %} 12 | 13 |
14 |
15 |
16 |
17 |
18 | 25 | 26 |
27 |
28 |
29 |
30 |
31 |
Search Hobby
32 | 39 |
40 |
41 |
42 |
AND
43 |
44 |
45 |
46 |
Search Interest
47 | 54 |
55 |
56 | 57 |
58 |
59 |
60 |
61 |
62 | 63 | 135 | 136 | {%endblock%} 137 | -------------------------------------------------------------------------------- /src/student_network/views/achievements.py: -------------------------------------------------------------------------------- 1 | """ 2 | Handles the view for achievements and related functionality. 3 | """ 4 | 5 | import sqlite3 6 | 7 | import student_network.helpers.helper_achievements as helper_achievements 8 | import student_network.helpers.helper_connections as helper_connections 9 | import student_network.helpers.helper_general as helper_general 10 | import student_network.helpers.helper_profile as helper_profile 11 | from flask import Blueprint, render_template, request, session 12 | 13 | achievements_blueprint = Blueprint( 14 | "achievements", __name__, static_folder="static", template_folder="templates" 15 | ) 16 | 17 | 18 | @achievements_blueprint.route("/achievements", methods=["GET"]) 19 | def achievements() -> object: 20 | """ 21 | Display achievements which the user has unlocked/locked. 22 | 23 | Returns: 24 | The web page for viewing achievements. 25 | """ 26 | unlocked_achievements, locked_achievements = helper_achievements.get_achievements( 27 | session["username"] 28 | ) 29 | 30 | # Displays the percentage of achievements unlocked. 31 | percentage = int( 32 | 100 33 | * len(unlocked_achievements) 34 | / (len(unlocked_achievements) + len(locked_achievements)) 35 | ) 36 | percentage_color = "green" 37 | if percentage < 75: 38 | percentage_color = "yellow" 39 | if percentage < 50: 40 | percentage_color = "orange" 41 | if percentage < 25: 42 | percentage_color = "red" 43 | 44 | # Award achievement ID 3 - Show it off if necessary 45 | helper_achievements.apply_achievement(session["username"], 3) 46 | 47 | session["prev-page"] = request.url 48 | return render_template( 49 | "achievements.html", 50 | unlocked_achievements=unlocked_achievements, 51 | locked_achievements=locked_achievements, 52 | requestCount=helper_connections.get_connection_request_count(), 53 | allUsernames=helper_general.get_all_usernames(), 54 | percentage=percentage, 55 | percentage_color=percentage_color, 56 | notifications=helper_general.get_notifications(), 57 | ) 58 | 59 | 60 | @achievements_blueprint.route("/leaderboard", methods=["GET"]) 61 | def leaderboard() -> object: 62 | """ 63 | Displays leaderboard of users with the most experience. 64 | 65 | Returns: 66 | The web page for viewing rankings. 67 | """ 68 | with sqlite3.connect("db.sqlite3") as conn: 69 | cur = conn.cursor() 70 | cur.execute("SELECT * FROM UserLevel ORDER BY experience DESC") 71 | top_users = cur.fetchall() 72 | if top_users: 73 | total_user_count = len(top_users) 74 | 75 | my_ranking = helper_achievements.binary_search( 76 | top_users, 77 | (session["username"], helper_general.get_exp(session["username"])), 78 | ) 79 | 80 | top_users = top_users[0 : min(25, len(top_users))] 81 | top_users = list( 82 | map( 83 | lambda x: ( 84 | x[0], 85 | x[1], 86 | helper_profile.get_profile_picture(x[0]), 87 | helper_profile.get_level(x[0]), 88 | helper_profile.get_degree(x[0])[1], 89 | ), 90 | top_users, 91 | ) 92 | ) 93 | percent = int(100 * (my_ranking / total_user_count)) 94 | session["prev-page"] = request.url 95 | if "error" in session: 96 | errors = session["error"] 97 | session.pop("error", None) 98 | return render_template( 99 | "leaderboard.html", 100 | leaderboard=top_users, 101 | requestCount=helper_connections.get_connection_request_count(), 102 | allUsernames=helper_general.get_all_usernames(), 103 | myRanking=my_ranking, 104 | totalUserCount=total_user_count, 105 | percent=percent, 106 | errors=errors, 107 | notifications=helper_general.get_notifications(), 108 | ) 109 | else: 110 | return render_template( 111 | "leaderboard.html", 112 | leaderboard=top_users, 113 | requestCount=helper_connections.get_connection_request_count(), 114 | allUsernames=helper_general.get_all_usernames(), 115 | myRanking=my_ranking, 116 | totalUserCount=total_user_count, 117 | percent=percent, 118 | notifications=helper_general.get_notifications(), 119 | ) 120 | -------------------------------------------------------------------------------- /src/student_network/helpers/helper_login.py: -------------------------------------------------------------------------------- 1 | """ 2 | Performs checks and actions to help the login system work effectively. 3 | """ 4 | import re 5 | from typing import Tuple, List 6 | 7 | from email_validator import validate_email, EmailNotValidError 8 | 9 | 10 | def validate_registration( 11 | cur, 12 | username: str, 13 | full_name: str, 14 | password: str, 15 | password_confirm: str, 16 | email: str, 17 | terms: str, 18 | ) -> Tuple[bool, List[str]]: 19 | """ 20 | Validates the registration details to ensure that the email address is 21 | valid, and that the passwords in the form match. 22 | 23 | Args: 24 | cur: Cursor for the SQLite database. 25 | username: The username input by the user in the form. 26 | full_name: The full name input by the user in the form. 27 | password: The password input by the user in the form. 28 | password_confirm: The password confirmation input by the user in the 29 | form. 30 | email: The email address input by the user in the form. 31 | terms: The terms and conditions input checkbox. 32 | 33 | Returns: 34 | Whether the registration was valid, and the error message(s) if not. 35 | """ 36 | # Registration remains valid as long as it isn't caught by any checks. If 37 | # not, error messages will be provided to the user. 38 | valid = True 39 | message = [] 40 | 41 | # Checks that there are no null inputs. 42 | if ( 43 | username == "" 44 | or full_name == "" 45 | or password == "" 46 | or password_confirm == "" 47 | or email == "" 48 | ): 49 | message.append("Not all fields have been filled in!") 50 | valid = False 51 | 52 | # Checks that the username only contains valid characters. 53 | if username.isalnum() is False: 54 | message.append("Username must only contain letters and numbers!") 55 | valid = False 56 | # Checks that the username hasn't already been registered. 57 | cur.execute("SELECT * FROM Accounts WHERE username=?;", (username,)) 58 | if cur.fetchone() is not None: 59 | message.append("Username has already been registered!") 60 | valid = False 61 | 62 | # Checks that the full name doesn't exceed 40 characters. 63 | if len(full_name) > 40: 64 | message.append("Full name exceeds 40 characters!") 65 | valid = False 66 | # Checks that the full name only contains valid characters. 67 | if not all(x.isalpha() or x.isspace() for x in full_name): 68 | message.append("Full name must only contain letters and spaces!") 69 | valid = False 70 | 71 | # Checks that the email hasn't already been registered. 72 | cur.execute("SELECT * FROM Accounts WHERE email=?;", (email,)) 73 | if cur.fetchone() is not None: 74 | message.append("Email has already been registered!") 75 | valid = False 76 | # Checks that the email address has the correct format, checks whether it 77 | # exists, and isn't a blacklist email. 78 | try: 79 | valid_email = validate_email(email) 80 | # Updates with the normalised form of the email address. 81 | email = valid_email.email 82 | # If the format is valid, checks that the email address has the 83 | # University of Exeter domain. 84 | if re.search("@.*", email) is not None: 85 | domain = re.search("@.*", email).group() 86 | domain_type = domain.split(".")[1] 87 | if domain_type not in ["ac", "edu"]: 88 | valid = False 89 | message.append( 90 | "Email does not belong to a registered educational institute!" 91 | ) 92 | except EmailNotValidError: 93 | message.append("Email is invalid!") 94 | valid = False 95 | 96 | # Checks that the password has a minimum length of 8 characters, and at 97 | # least one number. 98 | if len(password) <= 7 or any(char.isdigit() for char in password) is False: 99 | message.append( 100 | "Password does not meet requirements! It must contain " 101 | "at least eight characters, including at least one " 102 | "number." 103 | ) 104 | valid = False 105 | # Checks that the passwords match. 106 | if password != password_confirm: 107 | message.append("Passwords do not match!") 108 | valid = False 109 | 110 | # Checks that the terms of service has been ticked. 111 | if terms is None: 112 | message.append("You must accept the terms of service!") 113 | valid = False 114 | 115 | return valid, message 116 | -------------------------------------------------------------------------------- /src/student_network/templates/terms.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Terms and Conditions{% endblock %} {% 11 | block content %} 12 |
13 |
14 |
15 |
16 |
Terms of Service
17 |
18 |
19 |
20 | 22 |

Reconnect – Terms of Service

23 | Please read these terms of service carefully before signing up for 24 | Reconnect. Reconnect is operated by Exeter University Group Software 25 | Engineering Project Group N. The Reconnect team is a group of 26 | university computer science students with the goal of working 27 | alongside the Exeter University faculty, and more importantly the 28 | student body, to develop a brand new social media connecting students 29 | through these isolating times. We wont charge anyone for the use of 30 | Reconnect. 31 |

Conditions of Use

32 | By using this website, you certify that you have read and reviewed 33 | this Agreement and that you agree to comply with its terms. If you do 34 | not want to be bound by the terms of this Agreement, you are advised 35 | to leave the website accordingly. Reconnect only grants use and access 36 | of this website, its products, and its services to those who have 37 | accepted its terms. 38 |

Privacy Policy

39 | Before you continue using our website, we advise you to read our 40 | privacy policy 41 | Privacy Policy regarding our user data 42 | collection. It will help you better understand our practices. 43 |

Intellectual Property

44 | You agree that all materials, products, and services provided on this 45 | website are the property of Reconnect, its affiliates, directors, 46 | officers, employees, agents, suppliers, or licensors including all 47 | copyrights, trade secrets, trademarks, patents, and other intellectual 48 | property. You also agree that you will not reproduce or redistribute 49 | the Reconnect intellectual property in any way, including electronic, 50 | digital, or new trademark registrations. You grant Reconnect a 51 | royalty-free and non-exclusive license to display, use, copy, 52 | transmit, and broadcast the content you upload and publish. For issues 53 | regarding intellectual property claims, contact our legal team at 54 | bg354@exeter.ac.uk in order to come to an agreement. 55 |

User Accounts

56 | As a user of this website, you will be asked to register with us and 57 | provide private information. You are responsible for ensuring the 58 | accuracy of this information, and you are responsible for maintaining 59 | the safety and security of your identifying information. You are also 60 | responsible for all activities that occur under your account or 61 | password. If you think there are any possible issues regarding the 62 | security of your account on the website, inform us immediately so we 63 | may address it accordingly. We reserve all rights to terminate 64 | accounts, edit or remove content and cancel orders in their sole 65 | discretion. 66 |

Applicable Law

67 | By visiting this website, you agree that the laws of the United 68 | Kingdom, without regard to principles of conflict laws, will govern 69 | these terms and conditions, or any dispute of any sort that might come 70 | between the Reconnect platform and you, or its business partners and 71 | associates. 72 |

Indemnification

73 | You agree to indemnify Reconnect and its affiliates and hold Reconnect 74 | harmless against legal claims and demands that may arise from your use 75 | or misuse of our services. We reserve the right to select our own 76 | legal counsel. 77 |

Limitation on Liability

78 | Reconnect is not liable for any damages that may occur to you as a 79 | result of your misuse of our website. Reconnect reserves the right to 80 | edit, modify, and change this Agreement any time. We shall let our 81 | users know of these changes through electronic mail. This Agreement is 82 | an understanding between Reconnect and the user, and this supersedes 83 | and replaces all prior agreements regarding the use of this website. 84 |
85 | 91 |
92 |
93 |
94 |
95 | {%endblock%} 96 | -------------------------------------------------------------------------------- /tests/test_login_system.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | 3 | import student_network.helpers.helper_login as helper_login 4 | from pytest_steps import test_steps 5 | 6 | 7 | @test_steps( 8 | "terms_not_accepted", 9 | "not_university_email", 10 | "not_valid_email", 11 | "password_mismatch", 12 | "password_no_number", 13 | "username_too_short", 14 | "full_name_contains_symbol", 15 | "username_contains_symbol", 16 | ) 17 | def test_invalid_registration(): 18 | """ 19 | Tests that invalid registration details are rejected. 20 | """ 21 | with sqlite3.connect("db.sqlite3") as conn: 22 | cur = conn.cursor() 23 | 24 | # Invalid registration as terms haven't been accepted. 25 | username = "goodname" 26 | full_name = "Good Name" 27 | password = "goodpw123" 28 | password_confirm = "goodpw123" 29 | email = "goodname@exeter.ac.uk" 30 | terms = None 31 | valid, _ = helper_login.validate_registration( 32 | cur, 33 | username, 34 | full_name, 35 | password, 36 | password_confirm, 37 | email, 38 | terms, 39 | ) 40 | assert valid is False 41 | yield 42 | 43 | # Invalid registration as the email address isn't from a university. 44 | email = "goodnamebademail@gmail.com" 45 | terms = "" 46 | valid, _ = helper_login.validate_registration( 47 | cur, 48 | username, 49 | full_name, 50 | password, 51 | password_confirm, 52 | email, 53 | terms, 54 | ) 55 | assert valid is False 56 | yield 57 | 58 | # Invalid registration as the email address has an invalid format. 59 | email = "bademail@" 60 | valid, _ = helper_login.validate_registration( 61 | cur, 62 | username, 63 | full_name, 64 | password, 65 | password_confirm, 66 | email, 67 | terms, 68 | ) 69 | assert valid is False 70 | yield 71 | 72 | # Invalid registration as the passwords don't match. 73 | password_confirm = "mismatch" 74 | email = "goodname@exeter.ac.uk" 75 | valid, _ = helper_login.validate_registration( 76 | cur, 77 | username, 78 | full_name, 79 | password, 80 | password_confirm, 81 | email, 82 | terms, 83 | ) 84 | assert valid is False 85 | yield 86 | 87 | # Invalid registration as the password doesn't contain a number. 88 | password = "badpassword" 89 | password_confirm = "badpassword" 90 | email = "goodname@exeter.ac.uk" 91 | valid, _ = helper_login.validate_registration( 92 | cur, 93 | username, 94 | full_name, 95 | password, 96 | password_confirm, 97 | email, 98 | terms, 99 | ) 100 | assert valid is False 101 | yield 102 | 103 | # Invalid registration as the password doesn't contain eight 104 | # characters. 105 | password = "badpw1" 106 | password_confirm = "badpw1" 107 | email = "goodname@exeter.ac.uk" 108 | valid, _ = helper_login.validate_registration( 109 | cur, 110 | username, 111 | full_name, 112 | password, 113 | password_confirm, 114 | email, 115 | terms, 116 | ) 117 | assert valid is False 118 | yield 119 | 120 | # Invalid registration as the full name contains a symbol. 121 | full_name = "Bad Name_" 122 | password = "goodpw123" 123 | password_confirm = "goodpw123" 124 | email = "goodname@exeter.ac.uk" 125 | valid, _ = helper_login.validate_registration( 126 | cur, 127 | username, 128 | full_name, 129 | password, 130 | password_confirm, 131 | email, 132 | terms, 133 | ) 134 | assert valid is False 135 | yield 136 | 137 | # Invalid registration as the username contains a symbol. 138 | username = "b@dname" 139 | full_name = "Good Name" 140 | password = "goodpw123" 141 | password_confirm = "goodpw123" 142 | email = "goodname@exeter.ac.uk" 143 | valid, _ = helper_login.validate_registration( 144 | cur, 145 | username, 146 | full_name, 147 | password, 148 | password_confirm, 149 | email, 150 | terms, 151 | ) 152 | assert valid is False 153 | yield 154 | 155 | 156 | def test_valid_registration(): 157 | """ 158 | Tests that valid registration details are accepted. 159 | """ 160 | with sqlite3.connect("db.sqlite3") as conn: 161 | cur = conn.cursor() 162 | valid, _ = helper_login.validate_registration( 163 | cur, 164 | "goodname", 165 | "Good Name", 166 | "goodpw123", 167 | "goodpw123", 168 | "goodname@exeter.ac.uk", 169 | "", 170 | ) 171 | assert valid is True 172 | 173 | 174 | def test_null_registration(): 175 | """ 176 | Tests that null registration details are rejected. 177 | """ 178 | with sqlite3.connect("db.sqlite3") as conn: 179 | cur = conn.cursor() 180 | valid, _ = helper_login.validate_registration(cur, "", "", "", "", "", "") 181 | assert valid is False 182 | -------------------------------------------------------------------------------- /src/student_network/templates/register.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Register{% endblock %} {% block 11 | content %} 12 |
13 |
14 |
15 |
16 |
Register
17 |
18 |
19 | {% if errors %} 20 |
21 |
Incorrect Credentials
22 | {% for item in errors %} 23 |

{{ item }}

24 | {% endfor %} 25 |
26 | {% endif %} {% if "register" in notifications %} 27 |
28 |
Success
29 |

Account created successfully. You can now login.

30 |
31 | {% endif %} 32 |
33 |
34 | 35 | 37 | 38 | 48 | 53 |
54 |
55 | 56 | 60 | 67 |
68 | 69 |
70 | 71 | 76 | 83 |
84 |
85 | 86 | 87 |
88 |
89 | 90 | 95 |
96 |
97 | 98 |
99 | Student 105 | 112 |
113 |
114 |
115 |
116 | 117 | 121 |
122 |
123 |
124 |
125 |
126 |

127 | Already have an account? 128 | Login here. 130 |

131 |
132 | 133 |
134 |
135 |
136 | 137 | 160 | 161 | {% endblock %} 162 | -------------------------------------------------------------------------------- /src/student_network/templates/quiz.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Quiz{% endblock %} {% block content 11 | %} 12 | 13 | 19 | 20 |
21 |
22 | {%for index in range(questions|length)%} 23 |
24 | 31 |
32 |

{{quiz_name}}

33 |
{{quiz_author}}
34 |
35 |
36 | 39 |
40 |
41 | 44 |
45 |
46 | 49 |
50 |
51 | 54 |
55 |
56 | 59 |
60 |
61 | {%endfor%} 62 |
63 |
64 |
65 |
71 | Previous 72 |
73 |
74 |
75 |
76 |
82 | Next 83 |
84 |
85 |
86 |
87 | 95 | 96 | 97 | 98 | 99 |
100 |
101 |
102 | 103 | 104 | 105 | 189 | 190 | {%endblock%} 191 | -------------------------------------------------------------------------------- /src/student_network/templates/request.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Request{% endblock %} {% block 11 | content %} 12 |
13 | {%if requests|length == 0 and connections|length == 0 %} 14 |
15 | No connections to show 16 |
17 | {% endif %} {%if requests|length > 0%} 18 |
19 | Your Connection Requests ({{requests|length}}) 20 |
21 |
22 | {% for i in range(requests|length) %} 23 |
24 |
25 |
29 | 33 |
34 |
35 |
39 | 43 |
44 |
45 | 46 |
47 | {{ requests[i] }} 50 |
51 |
52 | {% endfor %} 53 |
54 | {%endif%} {%if connections|length > 0%} 55 |
56 | Your Connections ({{connections|length}}) 57 |
58 |
59 | {% for connection in connections %} 60 |
61 |
62 |
66 | 70 |
71 |
72 | 73 |
74 | {{ connection[0] }} 77 | {% if connection[2]%} 78 | 79 | 80 | 81 | {%endif%} 82 |
83 |
84 | {% endfor %} 85 |
86 | {%endif%} {%if pending|length > 0%} 87 |
88 | Your Pending Connections ({{pending|length}}) 89 |
90 |
91 | {% for connection in pending %} 92 |
93 |
94 |
98 | 102 |
103 |
104 | 105 |
106 | {% if connection[2]%} 107 | 108 | 109 | 110 | {%endif%} 111 | {{ connection[0] }} 114 |
115 |
116 | {% endfor %} 117 |
118 | {%endif%} {%if blocked|length > 0%} 119 |
120 | Your Blocked Connections ({{blocked|length}}) 121 |
122 |
123 | {% for connection in blocked %} 124 |
125 |
126 |
130 | 134 |
135 |
136 | 137 |
138 | {% if connection[2]%} 139 | 140 | 141 | 142 | {%endif%} 143 | {{ connection[0] }} 146 |
147 |
148 | {% endfor %} 149 |
150 | {% endif %} 151 |
Recommended Connections
152 |
153 | {% if mutuals|length > 0 %} {% for i in range(mutuals|length) %} 154 |
155 |
156 |
161 | 165 |
166 |
167 | 168 |
169 | {{mutuals[i][0]}} 172 |
173 | {{mutuals[i][1] | safe}} 174 |
175 |
176 | {% endfor %} {% else %} 177 |
178 | Try connecting to more people or adding information to your profile to get 179 | connection recommendations 180 |
181 | {%endif%} 182 |
183 |
184 | {%endblock%} 185 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Student Network - Reconnect 2 | 3 | [![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 4 | ![CI](https://github.com/IsaacCheng9/student-network/actions/workflows/main.yml/badge.svg) 5 | 6 | A student network which promotes interaction within the student community. 7 | This aims to provide social opportunities by encouraging students to engage with 8 | each other in a fun and friendly online environment. 9 | 10 | ## Screenshots 11 | 12 | ![Profile](https://user-images.githubusercontent.com/47993930/148656392-dd7c2eb8-f495-45d2-ba2b-33a0c6aba631.png) 13 | ![Member List](https://user-images.githubusercontent.com/47993930/148656401-5a0e245f-183e-4535-97d1-58018b8b0644.png) 14 | ![Connection Requests](https://user-images.githubusercontent.com/47993930/148656410-df269b00-2ef9-4622-b6fc-4588e5d6a3d5.png) 15 | ![Posting to Feed](https://user-images.githubusercontent.com/47993930/148656419-cff8cd16-abdb-46d2-9a81-cd65669ef67c.png) 16 | ![Achievements](https://user-images.githubusercontent.com/47993930/148656427-21b91681-ebea-4898-8710-34d164c26963.png) 17 | ![Leaderboard](https://user-images.githubusercontent.com/47993930/148656429-c92419ff-9a23-44aa-8e78-41f7dbf3b528.png) 18 | ![Taking Quizzes](https://user-images.githubusercontent.com/47993930/148656440-ebe6b74b-ccdd-4277-a258-5bd467155bb2.png) 19 | ![Quiz Results](https://user-images.githubusercontent.com/47993930/148656480-ae105aaa-26b9-4111-a168-ba383bcc0cbf.png) 20 | 21 | ## Installation 22 | 23 | ### Python Version 24 | 25 | The application has been developed and tested to work on Python 3.8 and onwards. 26 | 27 | ### Running the Application Locally 28 | 29 | To run the application, you should follow the following steps: 30 | 31 | 1. Clone this GitHub repository. 32 | 2. Ensure that you're in the root directory: `student-network` 33 | 3. Install the required Python libraries: `pip install -r requirements.txt` 34 | 4. Install the code as a package on your local machine with the command: 35 | `pip install -e .` 36 | 5. Run the application with the command: `python -m student_network.app` 37 | 6. Navigate to http://127.0.0.1:5000/ in your web browser. 38 | 39 | ## Usage 40 | 41 | Upon opening the application, you will be greeted with a home page. From here, 42 | you can log into your existing account, or you can register a new account. It 43 | should be noted that registration will only work with university email 44 | addresses, and academics will be sent for manual verification after signing up. 45 | 46 | When you log in, you will be redirected to your profile page. This displays 47 | information about you, such as your hobbies, interests, your rarest achievements 48 | unlocked, and links to your social media profiles. Posts made by you are also 49 | displayed on this page. 50 | 51 | By default, some details will be filled in for you, such as your profile 52 | picture, bio, date of birth, and gender. You can edit all these details by 53 | pressing the 'Edit Profile' button. 54 | 55 | To search for other members on the Reconnect network, you can navigate to the 56 | 'Members' page using the navigation bar on the top. From here, you can search by 57 | the username, with the option of searching by a common hobby and/or interest 58 | too. Search results will be displayed live with their username and their degree, 59 | enabling you to visit the profiles of people and make connections. 60 | 61 | Connections may be formed with people, with the option of marking connections as 62 | close friends. Whereas connections must be accepted by people, you are able to 63 | mark people as close friends without making a request. This also means that a 64 | user may be your close friend, but you may not be their close friend; the close 65 | friend system is one-way. If you wish to limit interaction with another user, 66 | then you can block them. All these options may be accessed by navigating to a 67 | user's profile. 68 | 69 | You can view a list of your connections and pending connection requests on the 70 | Connections page. Users who you have marked as a close friend have an icon of a 71 | handshake next to them. 72 | 73 | On your feed, you can view all the posts of people who you have connected with. 74 | This is sorted in chronological order, with the newest posts appearing at the 75 | top. You can also make new posts from this page; these are categorised as a text 76 | post, an image post, or a link post. 77 | 78 | Achievements may be unlocked by performing tasks on the Reconnect network. You 79 | can view these on the Achievements page, which displays your progress with 80 | achievements as a percentage, and the achievements you have completed, starting 81 | from the most recent. This page also shows you which achievements you are yet to 82 | unlock. Hovering over each achievement shows the title of the achievement, 83 | description, and number of XP gained by unlocking it. Each of these achievements 84 | has its own unique icon. Watch out for hidden achievements which are not 85 | displayed until you unlock them; these will reward you with extra XP! 86 | 87 | You will level up your profile based on how much XP you have gained. This 88 | encourages some healthy competition in the Reconnect network. By interacting 89 | more in various parts of the application, you will quickly be able to climb up 90 | the leaderboard! 91 | 92 | We have also provided you with an easy way to test and share your knowledge on 93 | the Quizzes page. From here, you can create a quiz consisting of five 94 | multiple-choice questions for others to complete. You can also view and take 95 | part in quizzes made from other people. 96 | 97 | ## Demo Instructions 98 | 99 | For testing purposes, we have created a lot of accounts and sample data to make 100 | it easier to demo the product. The users `barn354` and `ic324`, both with 101 | password `Password01`, have been set up with a full student profile and multiple 102 | posts. 103 | 104 | The following dummy accounts have been set up: 105 | 106 | - `student1`, `student2`, `student3`, and `student4` for student accounts. 107 | - `staffuser` and `staffusertwo` for staff accounts. 108 | - `adminuser` for an administrator account. 109 | 110 | ## Documentation 111 | 112 | ### Requirements Analysis 113 | 114 | Requirements analysis has been encapsulated through our research documents on 115 | potential solutions, design thinking plan, and MoSCoW matrix. They can be found 116 | in the following path: [docs/requirements-analysis](docs/requirements-analysis) 117 | 118 | ### Poster 119 | 120 | A promotional poster has been designed for this project. It can be found in the 121 | following GitHub repository 122 | path: [docs/designs/poster.pdf](docs/designs/poster.pdf) 123 | -------------------------------------------------------------------------------- /src/student_network/helpers/helper_profile.py: -------------------------------------------------------------------------------- 1 | """ 2 | Performs checks and actions to help the profile system work effectively. 3 | """ 4 | import os 5 | import sqlite3 6 | import uuid 7 | from datetime import date, datetime 8 | from typing import List, Tuple 9 | 10 | import student_network.helpers.helper_general as helper_general 11 | from PIL import Image 12 | from werkzeug.utils import secure_filename 13 | 14 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 15 | DB_PATH = os.path.join(BASE_DIR, "db.sqlite3") 16 | 17 | 18 | def calculate_age(born: datetime) -> int: 19 | """ 20 | Calculates the user's current age based on their date of birth. 21 | 22 | Args: 23 | born: The user's date of birth. 24 | 25 | Returns: 26 | The age of the user in years. 27 | """ 28 | today = date.today() 29 | return today.year - born.year - ((today.month, today.day) < (born.month, born.day)) 30 | 31 | 32 | def get_degree(username: str) -> Tuple[int, str]: 33 | """ 34 | Gets the degree of a user. 35 | 36 | Args: 37 | username: The username of the user's profile picture. 38 | 39 | Returns: 40 | The degree of the user. 41 | The degreeID of the user. 42 | """ 43 | with sqlite3.connect("db.sqlite3") as conn: 44 | cur = conn.cursor() 45 | cur.execute("SELECT degree FROM UserProfile WHERE username=?;", (username,)) 46 | degree_id = cur.fetchone() 47 | if degree_id: 48 | cur.execute("SELECT degree FROM Degree WHERE degreeId=?;", (degree_id[0],)) 49 | degree = cur.fetchone() 50 | return degree_id[0], degree[0] 51 | 52 | 53 | def get_level(username: str) -> List[int]: 54 | """ 55 | Gets the current user experience points, the experience points 56 | for the next level and the user's current level from the database. 57 | 58 | Args: 59 | username: The username of the user logged in. 60 | 61 | Returns: 62 | The user's level, XP, and XP to reach the next level. 63 | """ 64 | level = 1 65 | xp_next_level = 100 66 | xp_increase_per_level = 15 67 | 68 | exp = helper_general.get_exp(username) 69 | while exp >= xp_next_level: 70 | level += 1 71 | exp -= xp_next_level 72 | xp_next_level += xp_increase_per_level 73 | 74 | return [level, exp, xp_next_level] 75 | 76 | 77 | def get_profile_picture(username: str) -> str: 78 | """ 79 | Gets the profile picture of a user. 80 | 81 | Args: 82 | username: The username of the user's profile picture. 83 | 84 | Returns: 85 | The profile picture of the user. 86 | """ 87 | with sqlite3.connect("db.sqlite3") as conn: 88 | cur = conn.cursor() 89 | cur.execute( 90 | "SELECT profilepicture FROM UserProfile WHERE username=?;", (username,) 91 | ) 92 | row = cur.fetchone() 93 | if row: 94 | return row[0] 95 | 96 | 97 | def read_socials(username: str): 98 | """ 99 | Args: 100 | username: The username whose socials to check. 101 | 102 | Returns: 103 | The social media accounts of that user. 104 | """ 105 | with sqlite3.connect("db.sqlite3") as conn: 106 | cur = conn.cursor() 107 | socials = {} 108 | # Gets the user's socials 109 | cur.execute( 110 | "SELECT social, link from UserSocial WHERE username=?;", (username,) 111 | ) 112 | row = cur.fetchall() 113 | if len(row) > 0: 114 | for item in row: 115 | socials[item[0]] = item[1] 116 | return socials 117 | 118 | 119 | def validate_edit_profile( 120 | bio: str, gender: str, dob: str, hobbies: list, interests: list 121 | ) -> Tuple[bool, List[str]]: 122 | """ 123 | Validates the details in the profile editing form. 124 | 125 | Args: 126 | bio: The bio input by the user in the form. 127 | gender: The gender input selected by the user in the form. 128 | dob: The date of birth input selected by the user in the form. 129 | hobbies: The list of hobbies from the form. 130 | interests: The list of interests from the form. 131 | 132 | Returns: 133 | Whether profile editing was valid, and the error message(s) if not. 134 | """ 135 | # Editing profile remains valid as long as it isn't caught by any checks. 136 | # If not, error messages will be provided to the user. 137 | valid = True 138 | message = [] 139 | 140 | # Checks that the bio has a maximum of 160 characters. 141 | if len(bio) > 160: 142 | valid = False 143 | message.append("Bio must not exceed 160 characters!") 144 | 145 | # Checks that the gender is male, female, or other. 146 | if gender not in ["Male", "Female", "Other"]: 147 | valid = False 148 | message.append("Gender must be male, female, or other!") 149 | 150 | # Only performs check if a new date of birth was entered. 151 | if dob != "": 152 | # Converts date string to datetime. 153 | dob = datetime.strptime(dob, "%Y-%m-%d") 154 | # Checks that date of birth is a past date. 155 | if datetime.today() < dob: 156 | valid = False 157 | message.append("Date of birth must be a past date!") 158 | 159 | # Checks that each hobby has a maximum of 24 characters. 160 | for hobby in hobbies: 161 | if len(hobby) > 24: 162 | valid = False 163 | message.append("Hobbies must not exceed 24 characters!") 164 | break 165 | # Checks that each interest has a maximum of 24 characters. 166 | for interest in interests: 167 | if len(interest) > 24: 168 | valid = False 169 | message.append("Interests must not exceed 24 characters!") 170 | break 171 | 172 | return valid, message 173 | 174 | 175 | def validate_profile_pic(file) -> Tuple[bool, List[str], str]: 176 | """ 177 | Validates the file to check that it's a valid image. 178 | 179 | Args: 180 | file: The file uploaded by the user. 181 | 182 | Returns: 183 | Whether the file uploaded is a valid image, and any error messages. 184 | """ 185 | valid = True 186 | message = [] 187 | file_name_hashed = "" 188 | 189 | # Hashes the name of the file and resizes it. 190 | if helper_general.is_allowed_photo_file(file.filename): 191 | secure_filename(file.filename) 192 | file_name_hashed = str(uuid.uuid4()) 193 | file_path = os.path.join("./static/images" + "//avatars", file_name_hashed) 194 | img = Image.open(file) 195 | img = img.resize((400, 400)) 196 | img = img.convert("RGB") 197 | img.save(file_path + ".jpg") 198 | elif file: 199 | valid = False 200 | message.append("Your file must be an image.") 201 | 202 | return valid, message, file_name_hashed 203 | -------------------------------------------------------------------------------- /src/student_network/templates/achievements.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Achievements{% endblock %} {% block 11 | content %} 12 | 13 | 220 | 221 |
222 |
223 |
224 |
225 |
{{percentage}}%
226 |
achievements completed
227 |
228 | 229 |
230 | Completed ({{unlocked_achievements|length}}) 231 |
232 |
233 | 234 | {% for achievement in unlocked_achievements %} 235 |
240 | 243 |
244 | {% endfor %} 245 | 246 |
247 |
248 | Locked ({{locked_achievements|length}}) 249 |
250 |
251 | 252 | {% for achievement in locked_achievements %} 253 |
258 |
259 | 260 |
261 | 264 |
265 | {% endfor %} 266 |
267 |
268 | 269 | 270 | 271 | 272 | 273 | 274 | 277 | 278 | {%endblock%} 279 | -------------------------------------------------------------------------------- /src/student_network/templates/chat.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} {% block title %}Home Page{% endblock %} {% block 2 | content %} 3 | 4 | 58 | 59 |
60 |
61 |
62 |
63 | 66 |
67 |
68 |
69 | 92 |
93 |
94 | {% if showChat %} 95 |
99 |
100 |
101 |
102 | {% for message in messages %} 103 |
104 | {% if message[1] != prev %} {% if message[1] == username %} 105 |
106 | {% else %} 107 |
108 | {% endif %} 109 |
{{message[1]}}
110 | {% endif %} 111 |
{{message[0]}}
112 |
113 |
114 | {% set prev = message[1] %} {% endfor %} 115 |
116 |
117 |
118 |
119 |
120 | 121 |
127 | 128 |
129 | 130 | 139 |
140 |
141 |
142 | 143 |
144 |
145 | 146 |
147 |
148 | 149 |
150 |
151 |
152 |
153 |
154 | {% else %} 155 |
156 |
157 | Click a user on the left to begin chatting with them. 158 |
159 |
160 | {% endif %} 161 |
162 |
163 | 164 | 165 | 166 | 167 | 241 | 242 | {% endblock %} 243 |
244 | -------------------------------------------------------------------------------- /src/student_network/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 30 | Student Network - {% block title %}{% endblock %} 31 | 32 | 33 | 34 | 38 | 39 | 75 | 76 | 77 | 78 |
79 |
80 | 206 | 207 | {% block content %} {% endblock %} 208 |
209 | 210 | 211 | -------------------------------------------------------------------------------- /src/student_network/helpers/helper_general.py: -------------------------------------------------------------------------------- 1 | """ 2 | Performs checks and actions to help the general system work effectively. 3 | """ 4 | import os 5 | import sqlite3 6 | from datetime import datetime 7 | from math import floor 8 | from typing import Tuple 9 | 10 | import student_network.helpers.helper_profile as helper_profile 11 | from flask import session 12 | 13 | BASE_DIR = os.path.dirname(os.path.abspath(__file__)) 14 | DB_PATH = os.path.join(BASE_DIR, "db.sqlite3") 15 | 16 | 17 | def is_allowed_photo_file(file_name) -> bool: 18 | """ 19 | Checks if the file is an allowed type. 20 | 21 | Args: 22 | file_name: The name of the file uploaded by the user. 23 | 24 | Returns: 25 | Whether the file is allowed or not (True/False). 26 | """ 27 | return "." in file_name and file_name.rsplit(".", 1)[1].lower() in { 28 | "png", 29 | "jpg", 30 | "jpeg", 31 | "gif", 32 | } 33 | 34 | 35 | def display_short_notification_age(seconds): 36 | prefixes = ["y", "mo", "d", "h", "m", "s"] 37 | values = [3600 * 24 * 365, 3600 * 31 * 24, 3600 * 24, 3600, 60, 1] 38 | 39 | for i in range(len(prefixes)): 40 | if seconds >= values[i]: 41 | return str(floor(seconds / values[i])) + prefixes[i] 42 | 43 | return "Just Now" 44 | 45 | 46 | def get_all_connections(username: str) -> list: 47 | """ 48 | Gets a list of all usernames that are connected to the logged in user. 49 | 50 | Returns: 51 | A list of all usernames that are connected to the logged in user. 52 | """ 53 | with sqlite3.connect("db.sqlite3") as conn: 54 | cur = conn.cursor() 55 | cur.execute( 56 | "SELECT user2 FROM Connection " 57 | "WHERE user1=? AND connection_type='connected' UNION ALL " 58 | "SELECT user1 FROM Connection " 59 | "WHERE user2=? AND connection_type='connected'", 60 | (username, username), 61 | ) 62 | connections = cur.fetchall() 63 | 64 | return connections 65 | 66 | 67 | def get_all_usernames() -> list: 68 | """ 69 | Gets a list of all usernames that are registered. 70 | 71 | Returns: 72 | A list of all usernames that have been registered. 73 | """ 74 | with sqlite3.connect("db.sqlite3") as conn: 75 | cur = conn.cursor() 76 | cur.execute("SELECT username FROM Accounts") 77 | 78 | row = cur.fetchall() 79 | 80 | return row 81 | 82 | 83 | def get_notifications(): 84 | with sqlite3.connect("db.sqlite3") as conn: 85 | cur = conn.cursor() 86 | 87 | cur.execute( 88 | "SELECT body, date, url FROM notification WHERE username=? ORDER " 89 | "BY date DESC", 90 | (session["username"],), 91 | ) 92 | 93 | row = cur.fetchall() 94 | 95 | notification_metadata = list( 96 | map( 97 | lambda x: ( 98 | x[0], 99 | display_short_notification_age( 100 | ( 101 | datetime.now() 102 | - datetime.strptime(x[1], "%Y-%m-%d " "%H:%M:%S") 103 | ).total_seconds() 104 | ), 105 | x[2], 106 | ), 107 | row, 108 | ) 109 | ) 110 | 111 | return notification_metadata 112 | 113 | 114 | def check_level_exists(username: str, conn): 115 | """ 116 | Checks that a user has a record in the database for their level. 117 | 118 | Args: 119 | username: The username of the user to check. 120 | conn: The connection to the database. 121 | """ 122 | cur = conn.cursor() 123 | cur.execute("SELECT * FROM UserLevel WHERE username=?;", (username,)) 124 | if cur.fetchone() is None: 125 | cur.execute( 126 | "INSERT INTO UserLevel (username, experience) VALUES (?, ?);", (username, 0) 127 | ) 128 | conn.commit() 129 | 130 | 131 | def get_messages(username: str): 132 | """ 133 | Get messages between logged in user and each user with chats 134 | 135 | Args: 136 | username: user to get messages of 137 | """ 138 | with sqlite3.connect("db.sqlite3") as conn: 139 | cur = conn.cursor() 140 | 141 | cur.execute( 142 | "SELECT message, sender, date FROM PrivateMessages WHERE (sender=? AND receiver=?) ", 143 | (session["username"], username), 144 | ) 145 | row = cur.fetchall() 146 | 147 | cur.execute( 148 | "SELECT message, sender, date FROM PrivateMessages WHERE (sender=? AND receiver=?) ", 149 | (username, session["username"]), 150 | ) 151 | row += cur.fetchall() 152 | 153 | if row == []: 154 | return [[""], "", ""] 155 | 156 | row.sort(key=lambda x: x[2], reverse=True) 157 | 158 | return [row, "", ""] 159 | 160 | 161 | def recent_message(date: str) -> Tuple[str, int]: 162 | """ 163 | Get time since the most recent message 164 | 165 | Args: 166 | date: datetime of message 167 | 168 | Returns: 169 | [type]: [description] 170 | """ 171 | seconds = ( 172 | datetime.now() - datetime.strptime(date, "%Y-%m-%d " "%H:%M:%S") 173 | ).total_seconds() 174 | elapsed = display_short_notification_age(seconds) 175 | 176 | return (elapsed, seconds) 177 | 178 | 179 | def get_rooms(): 180 | """ 181 | Get chat rooms for user 182 | 183 | Returns: 184 | chat rooms of user 185 | """ 186 | chat_rooms = get_all_connections(session["username"]) 187 | chat_rooms = list( 188 | map(lambda x: (x[0], helper_profile.get_profile_picture(x[0])), chat_rooms) 189 | ) 190 | 191 | chat_rooms = [list(x) for x in chat_rooms] 192 | 193 | for i, room in enumerate(chat_rooms): 194 | message = get_messages(room[0]) 195 | if message[0][0] != "": 196 | message[1], message[2] = recent_message(message[0][0][2]) 197 | chat_rooms[i].append(message) 198 | 199 | actives = [x for x in chat_rooms if x[2][2] != ""] 200 | inactives = [x for x in chat_rooms if x[2][2] == ""] 201 | actives.sort(key=lambda x: x[2][2]) 202 | chat_rooms = actives + inactives 203 | 204 | return chat_rooms 205 | 206 | 207 | def one_exp(cur, username: str): 208 | """ 209 | Awards 1 exp point 210 | 211 | Args: 212 | username: user to award exp to 213 | """ 214 | cur.execute( 215 | "UPDATE UserLevel SET experience = experience + 1 WHERE username=?;", 216 | (username,), 217 | ) 218 | 219 | 220 | def get_exp(username: str): 221 | """ 222 | Get current exp of given user 223 | 224 | Args: 225 | username: user to find exp value of 226 | 227 | Returns: 228 | exp of user 229 | """ 230 | with sqlite3.connect("db.sqlite3") as conn: 231 | cur = conn.cursor() 232 | check_level_exists(username, conn) 233 | # Get user experience 234 | cur.execute("SELECT experience FROM UserLevel WHERE username=?;", (username,)) 235 | row = cur.fetchone() 236 | 237 | return int(row[0]) 238 | 239 | 240 | def new_notification(body, url): 241 | now = datetime.now() 242 | 243 | with sqlite3.connect("db.sqlite3") as conn: 244 | cur = conn.cursor() 245 | 246 | cur.execute( 247 | "INSERT INTO notification (username, body, date, url) VALUES (?, " 248 | "?, ?, ?);", 249 | (session["username"], body, now.strftime("%Y-%m-%d %H:%M:%S"), url), 250 | ) 251 | 252 | conn.commit() 253 | 254 | 255 | def new_notification_username(username, body, url): 256 | now = datetime.now() 257 | 258 | with sqlite3.connect("db.sqlite3") as conn: 259 | cur = conn.cursor() 260 | 261 | cur.execute( 262 | "INSERT INTO notification (username, body, date, url) VALUES (?, " 263 | "?, ?, ?);", 264 | (username, body, now.strftime("%Y-%m-%d %H:%M:%S"), url), 265 | ) 266 | 267 | conn.commit() 268 | -------------------------------------------------------------------------------- /src/student_network/templates/quizzes.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 10 | {% extends "base.html" %} {% block title %}Quiz list{% endblock %} {% block 11 | content %} 12 | 13 | 14 | 21 | 22 |
23 | {% if errors %} 24 |
25 |
Error:
26 | {% for item in errors %} 27 |

{{ item }}

28 | {% endfor %} 29 |
30 | {% endif %} 31 | 32 |
37 |
38 | 39 | 40 |
41 | 42 |
43 | 44 | 47 | 48 | 49 |
50 |
51 | 52 |
53 | 74 |
75 | 76 | {% for quiz in quizzes %} 77 | 111 | {% endfor %} 112 | 113 | 114 | 115 | 232 | {% endblock %} 233 | --------------------------------------------------------------------------------