diff --git a/corpus/corpus/settings.py b/corpus/corpus/settings.py index 808339cd..72e453b7 100644 --- a/corpus/corpus/settings.py +++ b/corpus/corpus/settings.py @@ -67,6 +67,7 @@ "farewell.apps.FarewellConfig", "virtual_expo.apps.VirtualExpoConfig", "blog", + "smp.apps.SmpConfig", ] MIDDLEWARE = [ @@ -271,13 +272,15 @@ }, ], "toolbar": "DefaultToolbarConfig", - "removeDialogTabs": ";".join([ - "image:advanced", - "image:Link", - "link:upload", - "table:advanced", - "tableProperties:advanced", - ]), + "removeDialogTabs": ";".join( + [ + "image:advanced", + "image:Link", + "link:upload", + "table:advanced", + "tableProperties:advanced", + ] + ), "linkShowTargetTab": False, "linkShowAdvancedTab": False, "height": "250px", @@ -285,17 +288,18 @@ "forcePasteAsPlainText": True, "mathJaxClass": "mathjax-latex", "mathJaxLib": "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_SVG", - "extraPlugins": ",".join([ - "mathjax", - "codesnippet", - "image2", - "uploadimage", - "embed", - "uploadimage", - "tableresize", - ]), + "extraPlugins": ",".join( + [ + "mathjax", + "codesnippet", + "image2", + "uploadimage", + "embed", + "uploadimage", + "tableresize", + ] + ), "filebrowserUploadUrl": "/ckeditor/upload/?responseType=json", "filebrowserBrowseUrl": "/ckeditor/browse/", } } - diff --git a/corpus/corpus/urls.py b/corpus/corpus/urls.py index 3409ccd6..ce89b515 100644 --- a/corpus/corpus/urls.py +++ b/corpus/corpus/urls.py @@ -48,7 +48,8 @@ path("blog/", include("blog.urls")), path("athenaeum/", include("athenaeum.urls")), path("newsletter/", include("newsletter.urls")), + path("smp/", include("smp.urls")), ] if settings.DEBUG: - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/corpus/smp/__init__.py b/corpus/smp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/corpus/smp/admin.py b/corpus/smp/admin.py new file mode 100644 index 00000000..4b1010a7 --- /dev/null +++ b/corpus/smp/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from smp.models import Program +from smp.models import ProgramMember +from smp.models import Submission +from smp.models import Upload + +# Register your models here. +admin.site.register(Program) +admin.site.register(ProgramMember) +admin.site.register(Upload) +admin.site.register(Submission) diff --git a/corpus/smp/admin_urls.py b/corpus/smp/admin_urls.py new file mode 100644 index 00000000..d83fcb01 --- /dev/null +++ b/corpus/smp/admin_urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from smp import admin_views as views + +urlpatterns = [ + path("dashboard/", views.dashboard, name="smp_admin_dashboard"), + path( + "program//add_members/", + views.add_members, + name="smp_admin_add_members", + ), + path("/manage/", views.manage, name="smp_admin_manage"), +] diff --git a/corpus/smp/admin_views.py b/corpus/smp/admin_views.py new file mode 100644 index 00000000..5b16aa41 --- /dev/null +++ b/corpus/smp/admin_views.py @@ -0,0 +1,156 @@ +from accounts.models import ExecutiveMember +from accounts.models import User +from config.models import SIG +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.db import IntegrityError +from django.db.models import Count +from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect +from django.shortcuts import render +from smp.forms import AdminProgramMemberForm +from smp.forms import ProgramFilterForm +from smp.forms import ProgramForm +from smp.models import Program +from smp.models import ProgramMember + +from corpus.decorators import ensure_group_membership + + +@login_required +@ensure_group_membership(group_names=["smp_coordinator"]) +def dashboard(request): + programs = Program.objects.all().order_by("-pk") + + if request.method == "POST": + program_id = int(request.POST.get("program_id")) + action = request.POST.get("action") + program = get_object_or_404(Program, pk=program_id) + + if action == "hide_program": + if not program.hide_program: + program.hide_program = True + program.save() + messages.success(request, "Program has been hidden.") + elif action == "show_program": + if program.hide_program: + program.hide_program = False + program.save() + messages.success(request, "Program is now visible.") + elif action == "delete_program": + program.delete() + messages.success(request, "Program has been deleted.") + + return redirect("smp_admin_dashboard") + + form = ProgramFilterForm(request.GET) + if form.is_valid(): + try: + sig = int(form.cleaned_data.get("sig")) + if sig == -1: + programs = programs.annotate( + sig_count=Count( + "programmember__member__executivemember__sig", distinct=True + ) + ).filter(sig_count__gte=2) + elif sig != 0: + sig_obj = SIG.objects.filter(pk=sig).first() + if sig_obj: + programs = programs.annotate( + sig_count=Count( + "programmember__member__executivemember__sig", distinct=True + ) + ).filter( + sig_count=1, programmember__member__executivemember__sig=sig_obj + ) + except (ValueError, TypeError): + pass + + programs = programs.distinct() + args = {"programs": programs, "form": form} + + return render(request, "smp/admin/dashboard.html", args) + + +@login_required +@ensure_group_membership(group_names=["smp_coordinator"]) +def add_members(request, program_id): + program = Program.objects.get(pk=program_id) + mentors = program.mentors() + mentees = program.mentees() + + form = AdminProgramMemberForm(initial={"program": program}) + + if request.method == "POST": + if "add" in request.POST: + form = AdminProgramMemberForm(request.POST) + form.instance.program = program + if form.is_valid(): + member = form.cleaned_data["member"] + member_type = form.cleaned_data["member_type"] + + if member_type == "Mentor": + if not ExecutiveMember.objects.filter(user=member).exists(): + messages.error( + request, "Only Executive Members can be added as mentors." + ) + return redirect("smp_admin_add_members", program_id=program.id) + + try: + form.save() + except IntegrityError: + messages.error(request, "Member already added to the program.") + return redirect("smp_admin_add_members", program_id=program.id) + + messages.success(request, "Member added to program.") + return redirect("smp_admin_add_members", program_id=program.id) + elif "edit" in request.POST: + program_id = int(request.POST.get("program_id")) + member_id = int(request.POST.get("member_id")) + program = Program.objects.get(pk=program_id) + member = User.objects.get(pk=member_id) + + try: + program_member = ProgramMember.objects.get( + program=program, member=member + ) + program_member.delete() + except ProgramMember.DoesNotExist: + messages.error(request, "Member does not exist for this program.") + return redirect("smp_admin_add_members", program_id=program.id) + + messages.success(request, "Members edited in program.") + return redirect("smp_admin_add_members", program_id=program.id) + + args = { + "form": form, + "mentors": mentors, + "mentees": mentees, + "program": program, + } + + return render(request, "smp/admin/add_members.html", args) + + +@login_required +@ensure_group_membership(group_names=["smp_coordinator"]) +def manage(request, program_id): + program = Program.objects.get(pk=program_id) + form = ProgramForm(instance=program) + + if request.method == "POST": + form = ProgramForm(request.POST, request.FILES, instance=program) + if form.is_valid(): + form.save() + messages.success(request, "Program updated successfully!") + return redirect("smp_admin_dashboard") + + is_smp_coordinator = request.user.groups.filter(name="smp_coordinator").exists() + + args = { + "program": program, + "form": form, + "admin": is_smp_coordinator, + } + + return render(request, "smp/mentors/edit_program.html", args) diff --git a/corpus/smp/apps.py b/corpus/smp/apps.py new file mode 100644 index 00000000..408e22d1 --- /dev/null +++ b/corpus/smp/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SmpConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "smp" diff --git a/corpus/smp/forms.py b/corpus/smp/forms.py new file mode 100644 index 00000000..364ad8c7 --- /dev/null +++ b/corpus/smp/forms.py @@ -0,0 +1,71 @@ +from accounts.models import User +from config.models import SIG +from django import forms +from smp.models import Program +from smp.models import ProgramMember +from smp.models import Submission +from smp.models import Upload + +from corpus.forms import CorpusForm +from corpus.forms import CorpusModelForm + + +class ProgramFilterForm(CorpusForm): + sig = forms.ChoiceField(choices=[]) + year = forms.ChoiceField(choices=[], required=False) + + def __init__(self, *args, **kwargs): + super(ProgramFilterForm, self).__init__(*args, **kwargs) + + sig_choices = [(0, "All SIGs"), (-1, "Inter-SIG")] + list( + SIG.objects.values_list("id", "name") + ) + year_choices = [(0, "All Years")] + [ + (x, x) + for x in list(Program.objects.values_list("year", flat=True).distinct()) + ] + + self.fields["sig"].choices = sig_choices + self.fields["year"].choices = year_choices + + +class ProgramForm(CorpusModelForm): + class Meta: + model = Program + fields = [ + "title", + "abstract", + "thumbnail", + "year", + "description", + "hide_program", + ] + + +class ProgramMemberForm(CorpusModelForm): + class Meta: + model = ProgramMember + fields = ["member"] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + executive_users = User.objects.filter(executivemember__isnull=False).distinct() + self.fields["member"].queryset = executive_users + + +class UploadForm(CorpusModelForm): + class Meta: + model = Upload + fields = ["title", "upload_type", "content"] + + +class SubmissionForm(CorpusModelForm): + class Meta: + model = Submission + fields = ["title", "link"] + + +class AdminProgramMemberForm(CorpusModelForm): + class Meta: + model = ProgramMember + fields = ["member", "member_type"] diff --git a/corpus/smp/mentor_urls.py b/corpus/smp/mentor_urls.py new file mode 100644 index 00000000..e866aaa2 --- /dev/null +++ b/corpus/smp/mentor_urls.py @@ -0,0 +1,22 @@ +from django.urls import path +from smp import mentor_views as views + +urlpatterns = [ + path("dashboard/", views.dashboard, name="smp_mentors_dashboard"), + path("program/new/", views.new_program, name="smp_mentors_new_program"), + path( + "program//edit/", + views.edit_program, + name="smp_mentors_edit_program", + ), + path( + "program//add_members/", + views.add_members, + name="smp_mentors_add_members", + ), + path( + "program//upload/", + views.uploads, + name="smp_mentors_add_upload", + ), +] diff --git a/corpus/smp/mentor_views.py b/corpus/smp/mentor_views.py new file mode 100644 index 00000000..788a54ce --- /dev/null +++ b/corpus/smp/mentor_views.py @@ -0,0 +1,226 @@ +from accounts.models import ExecutiveMember +from accounts.models import User +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.db import IntegrityError +from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect +from django.shortcuts import render +from smp.forms import ProgramForm +from smp.forms import ProgramMemberForm +from smp.forms import UploadForm +from smp.models import Program +from smp.models import ProgramMember +from smp.models import Upload + +from corpus.decorators import ensure_exec_membership + + +@login_required +@ensure_exec_membership() +def dashboard(request): + programs = Program.objects.filter( + programmember__member=request.exec_member.user + ).order_by("-pk") + + admin_user = request.user.groups.filter(name="smp_coordinator").exists() + + if request.method == "POST": + program_id = int(request.POST.get("program_id")) + action = request.POST.get("action") + program = get_object_or_404(Program, pk=program_id) + + if action == "hide_program": + if not program.hide_program: + program.hide_program = True + program.save() + messages.success(request, "Program has been hidden.") + elif action == "show_program": + if program.hide_program: + program.hide_program = False + program.save() + messages.success(request, "Program is now visible.") + + return redirect("smp_mentors_dashboard") + + return render( + request, + "smp/mentors/dashboard.html", + {"programs": programs, "admin": admin_user}, + ) + + +@login_required +@ensure_exec_membership() +def new_program(request): + + existing_program = Program.objects.filter( + programmember__member=request.user + ).first() + + if existing_program: + messages.info(request, "You have already have a program.") + return redirect("smp_mentors_dashboard") + + form = ProgramForm() + + if request.method == "POST": + form = ProgramForm(request.POST, request.FILES) + if form.is_valid(): + + program = form.save(commit=False) + program.hide_program = True + program.save() + + ProgramMember.objects.create( + program=program, member=request.exec_member.user, member_type="Mentor" + ) + + messages.success(request, "Program saved successfully!") + return redirect("smp_mentors_dashboard") + + return render(request, "smp/mentors/new_program.html", {"form": form}) + + +@login_required +@ensure_exec_membership() +def edit_program(request, program_id): + program = Program.objects.get(pk=program_id) + mentors = program.mentors() + + if request.exec_member not in mentors: + messages.error(request, "You have not been added to this program.") + return redirect("smp_mentors_dashboard") + + form = ProgramForm(instance=program) + + if request.method == "POST": + form = ProgramForm(request.POST, request.FILES, instance=program) + if form.is_valid(): + form.save() + messages.success(request, "Program updated successfully!") + return redirect("smp_mentors_dashboard") + + args = {"program": program, "form": form} + + return render(request, "smp/mentors/edit_program.html", args) + + +@login_required +@ensure_exec_membership() +def add_members(request, program_id): + program = Program.objects.get(pk=program_id) + mentors = program.mentors() + mentees = program.mentees() + + if request.exec_member not in mentors: + messages.error(request, "You have not been added to this program.") + return redirect("smp_mentors_dashboard") + + form = ProgramMemberForm(initial={"program": program}) + + if request.method == "POST": + if "add" in request.POST: + form = ProgramMemberForm(request.POST) + form.instance.program = program + + if form.is_valid(): + member = form.cleaned_data["member"] + + # Set member_type automatically to "Mentor" + program_member = form.save(commit=False) + program_member.member_type = "Mentor" + + # Safety check (optional) + if not ExecutiveMember.objects.filter(user=member).exists(): + messages.error( + request, "Only Executive Members can be added as mentors." + ) + return redirect("smp_mentors_add_members", program_id=program.id) + + try: + program_member.save() + except IntegrityError: + messages.error(request, "Member already added to the program.") + return redirect("smp_mentors_add_members", program_id=program.id) + + messages.success(request, "Member added to program.") + return redirect("smp_mentors_add_members", program_id=program.id) + + elif "edit" in request.POST: + program_id = int(request.POST.get("program_id")) + member_id = int(request.POST.get("member_id")) + program = Program.objects.get(pk=program_id) + member = User.objects.get(pk=member_id) + + if member == request.exec_member.user: + messages.error(request, "You cannot remove yourself from the program.") + return redirect("smp_mentors_add_members", program_id=program.id) + + try: + program_member = ProgramMember.objects.get( + program=program, member=member + ) + program_member.delete() + except ProgramMember.DoesNotExist: + messages.error(request, "Member does not exist for this program.") + return redirect("smp_mentors_add_members", program_id=program.id) + + messages.success(request, "Members edited in program.") + return redirect("smp_mentors_add_members", program_id=program.id) + + args = { + "form": form, + "mentors": mentors, + "mentees": mentees, + "program": program, + } + + return render(request, "smp/mentors/add_members.html", args) + + +@login_required +@ensure_exec_membership() +def uploads(request, program_id): + program = get_object_or_404(Program, pk=program_id) + exec_member = get_object_or_404(ExecutiveMember, user=request.user) + mentors = program.mentors() + + if exec_member not in mentors: + messages.error(request, "You have not been added to this program.") + return redirect("smp_mentors_dashboard") + + form = UploadForm() + + if request.method == "POST": + if "add" in request.POST: + form = UploadForm(request.POST, request.FILES) + if form.is_valid(): + upload = form.save(commit=False) + upload.program = program + upload.upload_user = exec_member + upload.save() + messages.success(request, "Upload successful!") + return redirect("smp_mentors_dashboard") + + elif "delete" in request.POST: + upload_id = int(request.POST.get("upload_id", 0)) + try: + upload = Upload.objects.get(id=upload_id, program=program) + + if upload.upload_user == request.user or exec_member in mentors: + upload.delete() + messages.success(request, "Upload deleted successfully.") + else: + messages.error( + request, "You do not have permission to delete this upload." + ) + except Upload.DoesNotExist: + messages.error(request, "Upload does not exist.") + return redirect("smp_mentors_add_upload", program_id=program.id) + + uploads = Upload.objects.filter(program=program).order_by("-uploaded_at") + + args = {"uploads": uploads, "form": form, "program": program} + + return render(request, "smp/mentors/upload.html", args) diff --git a/corpus/smp/migrations/0001_initial.py b/corpus/smp/migrations/0001_initial.py new file mode 100644 index 00000000..b570375b --- /dev/null +++ b/corpus/smp/migrations/0001_initial.py @@ -0,0 +1,147 @@ +# Generated by Django 5.1.3 on 2025-05-23 17:32 +import ckeditor_uploader.fields +import django.db.models.deletion +from django.conf import settings +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("accounts", "0009_executivemember_hide_github_and_more"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Program", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=200)), + ("abstract", models.TextField()), + ("thumbnail", models.ImageField(upload_to="smp/programs/thumbnails")), + ("description", ckeditor_uploader.fields.RichTextUploadingField()), + ("year", models.PositiveSmallIntegerField()), + ("hide_program", models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name="Upload", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=255)), + ( + "upload_type", + models.CharField( + choices=[ + ("Assignment", "Assignment"), + ("Resource", "Resource"), + ], + max_length=10, + ), + ), + ("content", ckeditor_uploader.fields.RichTextUploadingField()), + ("uploaded_at", models.DateTimeField(auto_now_add=True)), + ( + "program", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="smp.program" + ), + ), + ( + "upload_user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="accounts.executivemember", + ), + ), + ], + ), + migrations.CreateModel( + name="Submission", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("link", models.URLField()), + ("submitted_at", models.DateTimeField(auto_now_add=True)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "assignment", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="submissions", + to="smp.upload", + ), + ), + ], + ), + migrations.CreateModel( + name="ProgramMember", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "member_type", + models.CharField( + choices=[("Mentor", "Mentor"), ("Mentee", "Mentee")], + max_length=10, + ), + ), + ( + "member", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "program", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="smp.program" + ), + ), + ], + options={ + "unique_together": {("program", "member")}, + }, + ), + ] diff --git a/corpus/smp/migrations/0002_submission_title.py b/corpus/smp/migrations/0002_submission_title.py new file mode 100644 index 00000000..585a896f --- /dev/null +++ b/corpus/smp/migrations/0002_submission_title.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.3 on 2025-05-24 05:35 +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + dependencies = [ + ("smp", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="submission", + name="title", + field=models.CharField(max_length=255), + preserve_default=False, + ), + ] diff --git a/corpus/smp/migrations/0003_remove_title_db_default.py b/corpus/smp/migrations/0003_remove_title_db_default.py new file mode 100644 index 00000000..6673ae8d --- /dev/null +++ b/corpus/smp/migrations/0003_remove_title_db_default.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.3 on 2025-05-24 05:39 +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + dependencies = [ + ("smp", "0002_submission_title"), + ] + + operations = [ + migrations.AlterField( + model_name="submission", + name="title", + field=models.CharField(max_length=255), + ), + ] diff --git a/corpus/smp/migrations/0004_alter_program_abstract_alter_program_description_and_more.py b/corpus/smp/migrations/0004_alter_program_abstract_alter_program_description_and_more.py new file mode 100644 index 00000000..c4724243 --- /dev/null +++ b/corpus/smp/migrations/0004_alter_program_abstract_alter_program_description_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.1.3 on 2025-05-25 05:50 + +import ckeditor_uploader.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('smp', '0003_remove_title_db_default'), + ] + + operations = [ + migrations.AlterField( + model_name='program', + name='abstract', + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name='program', + name='description', + field=ckeditor_uploader.fields.RichTextUploadingField(blank=True, null=True), + ), + migrations.AlterField( + model_name='program', + name='hide_program', + field=models.BooleanField(default=True), + ), + ] diff --git a/corpus/smp/migrations/__init__.py b/corpus/smp/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/corpus/smp/models.py b/corpus/smp/models.py new file mode 100644 index 00000000..1a6f0bb7 --- /dev/null +++ b/corpus/smp/models.py @@ -0,0 +1,98 @@ +from accounts.models import ExecutiveMember +from accounts.models import User +from ckeditor_uploader.fields import RichTextUploadingField +from config.models import SIG +from django.core.exceptions import ValidationError +from django.db import models + +# Create your models here. + + +class Program(models.Model): + title = models.CharField(max_length=200, blank=False, null=False) + abstract = models.TextField(blank=True, null=True) + thumbnail = models.ImageField( + upload_to="smp/programs/thumbnails", blank=False, null=False + ) + description = RichTextUploadingField(blank=True, null=True) + year = models.PositiveSmallIntegerField(blank=False, null=False) + hide_program = models.BooleanField(default=True) + + def __str__(self): + return self.title + + def mentors(self): + return ExecutiveMember.objects.filter( + user__programmember__program=self, user__programmember__member_type="Mentor" + ).distinct() + + def mentees(self): + return User.objects.filter( + programmember__program=self, programmember__member_type="Mentee" + ).distinct() + + def sigs(self): + return SIG.objects.filter( + executivemember__user__programmember__program=self + ).distinct() + + +class ProgramMember(models.Model): + MEMBER_TYPE_CHOICES = [ + ("Mentor", "Mentor"), + ("Mentee", "Mentee"), + ] + program = models.ForeignKey( + Program, blank=False, null=False, on_delete=models.CASCADE + ) + member = models.ForeignKey(User, blank=False, null=False, on_delete=models.CASCADE) + member_type = models.CharField( + max_length=10, choices=MEMBER_TYPE_CHOICES, blank=False, null=False + ) + + class Meta: + unique_together = (("program", "member"),) + + def __str__(self): + return f"{self.program} - {self.member}" + + +class Upload(models.Model): + UPLOAD_TYPE_CHOICES = [("Assignment", "Assignment"), ("Resource", "Resource")] + title = models.CharField(max_length=255, blank=False, null=False) + upload_user = models.ForeignKey( + ExecutiveMember, blank=False, null=False, on_delete=models.CASCADE + ) + program = models.ForeignKey( + Program, blank=False, null=False, on_delete=models.CASCADE + ) + upload_type = models.CharField( + max_length=10, choices=UPLOAD_TYPE_CHOICES, blank=False, null=False + ) + content = RichTextUploadingField(blank=False, null=False) + uploaded_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.upload_type} by {self.upload_user} - {self.title}" + + def is_assignment(self): + return self.upload_type == "Assignment" + + +class Submission(models.Model): + title = models.CharField(max_length=255, null=False, blank=False) + user = models.ForeignKey(User, null=False, blank=False, on_delete=models.CASCADE) + assignment = models.ForeignKey( + Upload, on_delete=models.CASCADE, related_name="submissions" + ) + link = models.URLField(blank=False, null=False) + submitted_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Submission by {self.user} for {self.assignment.title}" + + def clean(self): + # Enforce that assignment is of type "Assignment" + + if self.assignment.upload_type != "Assignment": + raise ValidationError("You can only submit to an Assignment.") diff --git a/corpus/smp/tests.py b/corpus/smp/tests.py new file mode 100644 index 00000000..601fc861 --- /dev/null +++ b/corpus/smp/tests.py @@ -0,0 +1,2 @@ +# from django.test import TestCase +# Create your tests here. diff --git a/corpus/smp/urls.py b/corpus/smp/urls.py new file mode 100644 index 00000000..dba11bc5 --- /dev/null +++ b/corpus/smp/urls.py @@ -0,0 +1,28 @@ +from django.urls import include +from django.urls import path +from smp import views + +urlpatterns = [ + path("", views.home, name="smp_home"), + path("/", views.programs_by_year, name="smp_programs_by_year"), + path("program/", views.program, name="smp_program"), + path( + "preview//", + views.preview_program, + name="smp_preview_program", + ), + path("program//upload_list", views.upload_list, name="upload_list"), + path( + "program/upload//submissions", + views.view_submission, + name="view_submission", + ), + path("program//view/", views.view_upload, name="view_upload"), + path( + "program/upload//submit", + views.create_submission, + name="create_submission", + ), + path("mentors/", include("smp.mentor_urls")), + path("admin/", include("smp.admin_urls")), +] diff --git a/corpus/smp/views.py b/corpus/smp/views.py new file mode 100644 index 00000000..4218c1a5 --- /dev/null +++ b/corpus/smp/views.py @@ -0,0 +1,312 @@ +from accounts.models import ExecutiveMember +from config.models import SIG +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django.db.models import Count +from django.shortcuts import get_object_or_404 +from django.shortcuts import redirect +from django.shortcuts import render +from smp.forms import ProgramFilterForm +from smp.forms import SubmissionForm +from smp.models import Program +from smp.models import ProgramMember +from smp.models import Submission +from smp.models import Upload + +from corpus.decorators import ensure_exec_membership + + +def home(request): + try: + ExecutiveMember.objects.get(user=request.user.id) + exec_member = True + except ExecutiveMember.DoesNotExist: + exec_member = False + + years = list(Program.objects.values_list("year", flat=True).distinct()) + return render( + request, "smp/home.html", {"years": years, "exec_member": exec_member} + ) + + +@login_required +def programs_by_year(request, year): + programs = Program.objects.filter(year=year, hide_program=False).order_by("-pk") + form = ProgramFilterForm(request.GET) + + if form.is_valid(): + try: + sig = int(form.cleaned_data.get("sig")) + if sig == -1: + programs = programs.annotate( + sig_count=Count( + "programmember__member__executivemember__sig", distinct=True + ) + ).filter(sig_count__gte=2) + elif sig != 0: + sig_obj = SIG.objects.filter(pk=sig).first() + + if sig_obj: + programs = programs.annotate( + sig_count=Count( + "programmember__member__executivemember__sig", distinct=True + ) + ).filter( + sig_count=1, programmember__member__executivemember__sig=sig_obj + ) + + except (ValueError, TypeError): + pass + + return render( + request, + "smp/programs_by_year.html", + {"programs": programs.distinct(), "year": year, "form": form}, + ) + + +@login_required +def program(request, program_id): + program = get_object_or_404(Program, pk=program_id) + return render( + request, + "smp/program.html", + { + "program": program, + "mentors": program.mentors(), + "mentees": program.mentees(), + }, + ) + + +@ensure_exec_membership() +def preview_program(request, program_id): + program = get_object_or_404(Program, pk=program_id) + return render( + request, + "smp/program.html", + { + "program": program, + "mentors": program.mentors(), + "mentees": program.mentees(), + "preview": True, + }, + ) + + +@login_required +def upload_list(request, program_id): + program = get_object_or_404(Program, pk=program_id) + user = request.user + + is_member = ProgramMember.objects.filter(program=program, member=user).exists() + is_coordinator = user.groups.filter(name__in=["smp_coordinator"]).exists() + + if not is_member and not is_coordinator: + messages.error(request, "You have not been added to this program.") + return redirect("smp_program", program_id=program.id) + + if is_coordinator: + member_type = "Mentor" + else: + program_member = ProgramMember.objects.get(program=program, member=request.user) + member_type = program_member.member_type + + if member_type == "Mentor": + + if request.method == "POST": + upload_id = int(request.POST.get("upload_id")) + upload = Upload.objects.get(id=upload_id) + + if "delete" in request.POST: + upload.delete() + messages.success(request, "Upload has been deleted.") + + return redirect("upload_list", program_id=program.id) + + uploads = Upload.objects.filter(program=program).order_by("-uploaded_at") + return render( + request, + "smp/upload_list.html", + { + "program": program, + "mentors": program.mentors(), + "mentees": program.mentees(), + "uploads": uploads, + "member_type": member_type, + }, + ) + + +@login_required +def view_upload(request, upload_id): + upload = get_object_or_404(Upload, id=upload_id) + user = request.user + program = upload.program + + is_member = ProgramMember.objects.filter(program=program, member=user).exists() + is_coordinator = user.groups.filter(name__in=["smp_coordinator"]).exists() + + if not is_member and not is_coordinator: + messages.error(request, "You have not been added to this program.") + return redirect("smp_program", program_id=program.id) + + return render( + request, + "smp/mentors/view_upload.html", + {"upload": upload, "program": program}, + ) + + +@login_required +def view_submission(request, upload_id): + upload = get_object_or_404(Upload, id=upload_id) + user = request.user + program = upload.program + + is_member = ProgramMember.objects.filter(program=program, member=user).exists() + is_coordinator = user.groups.filter(name__in=["smp_coordinator"]).exists() + + if not is_member and not is_coordinator: + messages.error(request, "You have not been added to this program.") + return redirect("smp_program", program_id=program.id) + + mentors = program.mentors() + mentor_users = [mentor.user for mentor in mentors] + mentees = program.mentees() + + if user in mentor_users or is_coordinator: + submissions = Submission.objects.filter(assignment=upload) + + if request.method == "POST": + submission_id = int(request.POST.get("submission_id")) + submission = Submission.objects.get(id=submission_id, assignment=upload) + + if "delete" in request.POST: + submission.delete() + messages.success(request, "Program has been deleted.") + + return redirect("view_submission", upload_id=upload.id) + return render( + request, + "smp/mentors/submission_list.html", + {"submissions": submissions, "admin": is_coordinator}, + ) + + elif user in mentees: + submission = Submission.objects.filter( + user=request.user, assignment=upload + ).first() + + form = None + + if request.method == "POST": + if "add" in request.POST: + if submission: + form = SubmissionForm(request.POST, instance=submission) + else: + submission = Submission(user=request.user, assignment=upload) + form = SubmissionForm(request.POST, instance=submission) + + if form.is_valid(): + form.save() + messages.success(request, "Submission successful!") + return redirect("upload_list", program_id=program.id) + + elif "delete" in request.POST: + submission_id_str = request.POST.get("submission_id") + if submission_id_str and submission_id_str.isdigit(): + submission_id = int(submission_id_str) + try: + submission_to_delete = Submission.objects.get( + id=submission_id, assignment=upload + ) + + if ( + submission_to_delete.user == request.user + or request.user in mentors + ): + submission_to_delete.delete() + messages.success( + request, "Submission deleted successfully." + ) + else: + messages.error( + request, + "You do not have permission to delete this submission.", + ) + except Submission.DoesNotExist: + messages.error(request, "Submission does not exist.") + else: + messages.error(request, "Invalid submission id.") + + return redirect("upload_list", program_id=program.id) + + # If form is still None (e.g., GET request or after delete), initialize it + if form is None: + form = SubmissionForm(instance=submission) + + return render( + request, + "smp/view_submission.html", + { + "upload": upload, + "submission": submission, + "form": form, + "program": program, + "admin": is_coordinator, + }, + ) + + else: + messages.error(request, "You are not authorized to view this submission.") + return redirect("smp_program", program_id=program.id) + + +@login_required +def create_submission(request, upload_id): + upload = get_object_or_404(Upload, id=upload_id) + + if upload.upload_type != "Assignment": + messages.error(request, "Submissions are only allowed for assignments.") + return redirect("smp_program", program_id=upload.program.id) + + program = upload.program + + try: + program_member = ProgramMember.objects.get(program=program, member=request.user) + except ProgramMember.DoesNotExist: + messages.error(request, "You are not a member of this program.") + return redirect("smp_program", program_id=program.id) + + if program_member.member_type != "Mentee": + messages.error(request, "Only mentees can submit assignments.") + return redirect("smp_program", program_id=program.id) + + submission = Submission.objects.filter(user=request.user, assignment=upload).first() + + if submission: + messages.info(request, "You have already submitted this assignment.") + return redirect("view_submission", upload_id=upload.id) + + if request.method == "POST": + if submission: + form = SubmissionForm(request.POST, instance=submission) + else: + submission = Submission(user=request.user, assignment=upload) + form = SubmissionForm(request.POST, instance=submission) + + if form.is_valid(): + form.save() + messages.success(request, "Submission saved successfully.") + return redirect("view_submission", upload_id=upload.id) + else: + messages.error(request, "Please correct the errors below.") + else: + form = SubmissionForm(instance=submission) + + return render( + request, + "smp/create_submission.html", + {"form": form, "upload": upload, "program": program}, + ) diff --git a/corpus/templates/components/navbar_list.html b/corpus/templates/components/navbar_list.html index 9576c7a9..b0a52d8b 100644 --- a/corpus/templates/components/navbar_list.html +++ b/corpus/templates/components/navbar_list.html @@ -103,13 +103,13 @@
  • - + Membership Drive
    • - Registration Form + Registration Form
    • @@ -132,6 +132,12 @@
    • +
    • + + SMP + +
    • +
    • Gyan @@ -142,4 +148,4 @@ Virtual Expo -
    • \ No newline at end of file + diff --git a/corpus/templates/smp/admin/add_members.html b/corpus/templates/smp/admin/add_members.html new file mode 100644 index 00000000..4d999771 --- /dev/null +++ b/corpus/templates/smp/admin/add_members.html @@ -0,0 +1,119 @@ +{% extends "smp/base.html" %} + +{% block title %} + Add Members + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Add Members" %} +
      +
      +

      Mentors:

      + + + + + + + + + + + {% for mentor in mentors %} + + + + + + + {% endfor %} + +
      NameSIGAction
      {{ forloop.counter }}{{ mentor.user }}{{ mentor.sig }} +
      + {% csrf_token %} + + + +
      +
      +
      +
      +

      Mentees:

      + + + + + + + + + + {% for mentee in mentees %} + + + + + + {% endfor %} + +
      NameAction
      {{ forloop.counter }}{{ mentee }} +
      + {% csrf_token %} + + + +
      +
      +
      +
      +
      + {% csrf_token %} + + {% if form.non_field_errors %} + {% for error in form.non_field_errors %} + + {% endfor %} + {% endif %} + +
      + + {{ form.member }} + {% if form.member.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.member_type }} + {% if form.member_type.errors %} +
      + +
      + {% endif %} +
      + + +
      +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/admin/dashboard.html b/corpus/templates/smp/admin/dashboard.html new file mode 100644 index 00000000..35961905 --- /dev/null +++ b/corpus/templates/smp/admin/dashboard.html @@ -0,0 +1,152 @@ +{% extends "virtual_expo/base.html" %} + +{% block title %} + Program Coordinator Dashboard + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Program Coordinator Dashboard" %} +
      +
      +
      +
      +
      + + {{ form.sig }} +
      +
      + + {{ form.year }} +
      +
      + +
      +
      +
      +
      +
      + + + + + + + + + + + + + {% for program in programs %} + + + + + + + + + {% endfor %} + +
      TitleYearSIGsVisibility StatusAction
      {{ forloop.counter }}{{ program.title }}{{ program.year }} + {% for sig in program.sigs %} +
      {{ sig }}
      + {% endfor %} +
      + {% if not program.hide_program %} +
      Visible
      + {% else %} +
      Hidden
      + {% endif %} +
      +
      + + Preview Program + + {% if not program.hide_program %} + + {% else %} + + {% endif %} + + Add Members + + + + + + Manage + + Manage Uploads + +
      +
      + + + + +
      +
      + + +{% endblock %} diff --git a/corpus/templates/smp/base.html b/corpus/templates/smp/base.html new file mode 100644 index 00000000..6cdcba36 --- /dev/null +++ b/corpus/templates/smp/base.html @@ -0,0 +1,5 @@ +{% extends "base.html" %} + +{% block title %} + | Summer Mentorship Programes +{% endblock %} diff --git a/corpus/templates/smp/create_submission.html b/corpus/templates/smp/create_submission.html new file mode 100644 index 00000000..d57e385a --- /dev/null +++ b/corpus/templates/smp/create_submission.html @@ -0,0 +1,60 @@ +{% extends "smp/base.html" %} + +{% block title %} + Add Members + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Create Submission" %} +
      +
      +
      +

      Create Submission for {{ upload.title }}

      +
      + {% csrf_token %} + + {% if form.non_field_errors %} + {% for error in form.non_field_errors %} + + {% endfor %} + {% endif %} + +
      + + {{ form.title }} + {% if form.title.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.link }} + {% if form.link.errors %} +
      + +
      + {% endif %} +
      + + +
      +
      +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/header.html b/corpus/templates/smp/header.html new file mode 100644 index 00000000..03b9133d --- /dev/null +++ b/corpus/templates/smp/header.html @@ -0,0 +1,12 @@ +
      +

      + {% if text %} + {{ text }} + {% else %} + Summer Mentorship Program + {% endif %} + {% if year %} + {{ year }} + {% endif %} +

      +
      diff --git a/corpus/templates/smp/home.html b/corpus/templates/smp/home.html new file mode 100644 index 00000000..fd690436 --- /dev/null +++ b/corpus/templates/smp/home.html @@ -0,0 +1,35 @@ +{% extends "smp/base.html" %} + +{% block title %} + Home + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" %} +
      + {% if exec_member %} + + Go to Mentors Dashboard + + {% endif %} +

      + Welcome to IEEE NITK's Summer Mentorship Programes! + We are grateful for the opportunity to share our knowledge + and skills through the Summer Mentorship Programs, and thrilled + to support the growth and learning of aspiring minds each year. +

      +

      + Programs can be seen using the following links, + seperated by year. +

      +
        + {% for year in years %} +
      • + {{ year }} +
      • + {% endfor %} +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/mentors/add_members.html b/corpus/templates/smp/mentors/add_members.html new file mode 100644 index 00000000..e63f1d0f --- /dev/null +++ b/corpus/templates/smp/mentors/add_members.html @@ -0,0 +1,107 @@ +{% extends "smp/base.html" %} + +{% block title %} + Add Members + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Add Mentors" %} +
      +
      +

      Mentors:

      + + + + + + + + + + + {% for mentor in mentors %} + + + + + + + {% endfor %} + +
      NameSIGAction
      {{ forloop.counter }}{{ mentor.user }}{{ mentor.sig }} +
      + {% csrf_token %} + + + +
      +
      +
      +
      +

      Mentees:

      + + + + + + + + + + {% for mentee in mentees %} + + + + + + {% endfor %} + +
      NameAction
      {{ forloop.counter }}{{ mentee }} +
      + {% csrf_token %} + + + +
      +
      +
      +
      +
      + {% csrf_token %} + + {% if form.non_field_errors %} + {% for error in form.non_field_errors %} + + {% endfor %} + {% endif %} + +
      + + {{ form.member }} + {% if form.member.errors %} +
      + +
      + {% endif %} +
      + + +
      +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/mentors/dashboard.html b/corpus/templates/smp/mentors/dashboard.html new file mode 100644 index 00000000..8ee3fe10 --- /dev/null +++ b/corpus/templates/smp/mentors/dashboard.html @@ -0,0 +1,149 @@ +{% extends "smp/base.html" %} + +{% block title %} + Mentors Dashboard + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Mentors Dashboard" %} +
      +
      +

      My Programs

      + +
      + + + + + + + + + + + + {% if programs %} + {% for program in programs %} + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
      TitleVisibility StatusActions
      {{ forloop.counter }}{{ program.title }} + {% if not program.hide_program %} +
      Visible
      + {% else %} +
      Hidden
      + {% endif %} +
      + Edit + {% if not program.hide_program %} + View Program + {% else %} + + Preview Program + + {% endif %} + {% if not program.hide_program %} + + {% else %} + + Add Mentors + + + {% endif %} + + Manage Uploads + +
      + No programs yet. Create your first one! +
      + + + + +
      +
      +
      + + + + + + + + +{% endblock %} diff --git a/corpus/templates/smp/mentors/edit_program.html b/corpus/templates/smp/mentors/edit_program.html new file mode 100644 index 00000000..99128de0 --- /dev/null +++ b/corpus/templates/smp/mentors/edit_program.html @@ -0,0 +1,98 @@ +{% extends "smp/base.html" %} + +{% block title %} + Edit {{ program.title }} + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text=program.title %} +
      + {% if admin %} +
      + {% else %} + + {% endif %} + + {% csrf_token %} + {{ form.media }} + + {% if form.non_field_errors %} + {% for error in form.non_field_errors %} + + {% endfor %} + {% endif %} + +
      + + {{ form.title }} + {% if form.title.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.abstract }} + {% if form.abstract.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.thumbnail }} + {% if form.thumbnail.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.year }} + {% if form.year.errors %} +
      + +
      + {% endif %} +
      + +
      + +
      + {{ form.description | safe }} +
      + {% if form.description.errors %} +
      + +
      + {% endif %} +
      + + +
      + +
      +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/mentors/new_program.html b/corpus/templates/smp/mentors/new_program.html new file mode 100644 index 00000000..c2d288a3 --- /dev/null +++ b/corpus/templates/smp/mentors/new_program.html @@ -0,0 +1,90 @@ +{% extends "smp/base.html" %} + +{% block title %} + New Program + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="New Program" %} +
      +
      + {% csrf_token %} + {{ form.media }} + + {% if form.non_field_errors %} + {% for error in form.non_field_errors %} + + {% endfor %} + {% endif %} + +
      + + {{ form.title }} + {% if form.title.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.abstract }} + {% if form.abstract.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.thumbnail }} + {% if form.thumbnail.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.year }} + {% if form.year.errors %} +
      + +
      + {% endif %} +
      + +
      + +
      + {{ form.description | safe }} +
      + {% if form.description.errors %} +
      + +
      + {% endif %} +
      + +
      + +
      +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/mentors/submission_list.html b/corpus/templates/smp/mentors/submission_list.html new file mode 100644 index 00000000..467c4cfe --- /dev/null +++ b/corpus/templates/smp/mentors/submission_list.html @@ -0,0 +1,70 @@ +{% extends "smp/base.html" %} + +{% block title %} + Submission + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Submissions" %} +
      +
      +
      + + + + + + + + + + {% if admin %} + + {% endif %} + + + + {% if submissions %} + {% for submission in submissions %} + + + + + + + {% if admin %} + + {% endif %} + + {% endfor %} + {% else %} + + + + {% endif %} + +
      Submission TitleSubmitted BySubmitted AtSubmission LinkAction
      {{ forloop.counter }}{{ submission.title }} + {{ submission.user }} + + {{ submission.submitted_at }} + + + {{ submission.link }} + + +
      + {% csrf_token %} + + +
      +
      + No submissions yet. Come back later! +
      +
      +
      +
      + +{% endblock %} diff --git a/corpus/templates/smp/mentors/upload.html b/corpus/templates/smp/mentors/upload.html new file mode 100644 index 00000000..9f12248a --- /dev/null +++ b/corpus/templates/smp/mentors/upload.html @@ -0,0 +1,112 @@ +{% extends "smp/base.html" %} + +{% block title %} + Uploads + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Uploads" %} +
      +
      +

      Uploads:

      + + + + + + + + + + + + + {% for upload in uploads %} + + + + + + + + + {% endfor %} + +
      Upload NameUploaded ByUpload TimeUpload TypeAction
      {{ forloop.counter }}{{ upload.title }}{{ upload.upload_user.user }}{{ upload.uploaded_at }}{{ upload.upload_type }} + + View + + +
      + {% csrf_token %} + + +
      +
      +
      +
      +
      + {% csrf_token %} + {{ form.media }} + + {% if form.non_field_errors %} + {% for error in form.non_field_errors %} + + {% endfor %} + {% endif %} + +
      + + {{ form.title }} + {% if form.title.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.upload_type }} + {% if form.upload_type.errors %} +
      + +
      + {% endif %} +
      + +
      + +
      + {{ form.content | safe }} +
      + {% if form.content.errors %} +
      + +
      + {% endif %} +
      + + +
      + + + Back To Home + +
      +
      +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/mentors/view_upload.html b/corpus/templates/smp/mentors/view_upload.html new file mode 100644 index 00000000..efad7402 --- /dev/null +++ b/corpus/templates/smp/mentors/view_upload.html @@ -0,0 +1,56 @@ +{% extends "smp/base.html" %} + +{% block title %} + {{ program.title }} + {{ block.super }} +{% endblock %} + +{% block content %} + +
      +
      +
      +

      {{ upload.title }}

      +
      + + {{ upload.upload_type }} + +
      +
      + +
      + +
      + {{ upload.content | safe }} +
      + +
      +
      + + + +
      +
      +
      + + + + +{% endblock %} diff --git a/corpus/templates/smp/program.html b/corpus/templates/smp/program.html new file mode 100644 index 00000000..c3e1710a --- /dev/null +++ b/corpus/templates/smp/program.html @@ -0,0 +1,156 @@ +{% extends "smp/base.html" %} + +{% block title %} + {{ program.title }} + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with year=program.year %} + + {% if preview %} +
      + Preview Mode - This is a draft version of the program +
      + {% endif %} + +
      +
      +
      +

      {{ program.title }}

      +
      + {% for sig in program.sigs %} + + {{ sig }} + + {% endfor %} +
      +
      + +
      + + {% if program.abstract %} + + {% endif %} + {% if program.abstract %} + + {% endif %} + +
      + {{ program.description | safe }} +
      + + + + +
      +

      Program Information

      + +
      + + + + + + + + + +
      +
      +
      +
      + +
      + {% if not preview %} +
      +

      Explore More Programs

      + + View All {{ program.year }} Programs + +
      + {% else %} + + {% endif %} +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/programs_by_year.html b/corpus/templates/smp/programs_by_year.html new file mode 100644 index 00000000..479d1be1 --- /dev/null +++ b/corpus/templates/smp/programs_by_year.html @@ -0,0 +1,65 @@ +{% extends "base.html" %} + +{% block title %} + Summer Mentorship Program {{ year }} + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with year=year %} +
      +
      +
      +
      +
      + + {{ form.sig }} +
      +
      + +
      +
      +
      +
      + +
      + {% if programs %} + {% for program in programs %} + + {% endfor %} + {% else %} +
      +

      No programs yet. Check back soon as we are uploading our programs!

      +
      + {% endif %} +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/upload_list.html b/corpus/templates/smp/upload_list.html new file mode 100644 index 00000000..a5f06cd4 --- /dev/null +++ b/corpus/templates/smp/upload_list.html @@ -0,0 +1,70 @@ +{% extends "smp/base.html" %} + +{% block title %} + Uploads + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Uploads" %} +
      +
      +

      Uploads:

      + + + + + + + + + + + + + {% if uploads %} + {% for upload in uploads %} + + + + + + + + + {% endfor %} + {% else %} + + + + {% endif %} + +
      Upload NameUploaded ByUpload TimeUpload TypeAction
      {{ forloop.counter }}{{ upload.title }}{{ upload.upload_user.user }}{{ upload.uploaded_at }}{{ upload.upload_type }} + + View + + {% if upload.upload_type == 'Assignment' %} + + View Submissions + + {% if member_type == "Mentee" %} + + Upload Submission + + {% endif %} + {% endif %} + {% if member_type == "Mentor" %} +
      + {% csrf_token %} + + +
      + {% endif %} +
      + No uploads yet. Come back later! +
      +
      +
      +{% endblock %} diff --git a/corpus/templates/smp/view_submission.html b/corpus/templates/smp/view_submission.html new file mode 100644 index 00000000..42e0b80f --- /dev/null +++ b/corpus/templates/smp/view_submission.html @@ -0,0 +1,102 @@ +{% extends "smp/base.html" %} + +{% block title %} + Add Members + {{ block.super }} +{% endblock %} + +{% block content %} + {% include "smp/header.html" with text="Submission" %} +
      +
      +
      +

      Submission for {{ submission.assignment.title }}

      + + + + + + + + + + + {% if submission and submission.id %} + + + + + + + {% else %} + + + + {% endif %} + +
      TitleLinkSubmitted AtAction
      {{ submission.title }} + + {{ submission.link|truncatechars:40 }} + + {{ submission.submitted_at|date:"M d, Y H:i" }} +
      + {% csrf_token %} + + +
      +
      + No submission available. +
      +
      +
      + +
      +

      Edit Submission for {{ submission.assignment.title }}:

      +
      + {% csrf_token %} + + {% if form.non_field_errors %} + {% for error in form.non_field_errors %} + + {% endfor %} + {% endif %} + +
      + + {{ form.title }} + {% if form.title.errors %} +
      + +
      + {% endif %} +
      + +
      + + {{ form.link }} + {% if form.link.errors %} +
      + +
      + {% endif %} +
      + + +
      +
      +
      +{% endblock %}