From 0504b881fbe914f610de46aa44133d7b3941caa5 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Wed, 8 Jan 2025 21:15:45 +0545 Subject: [PATCH] update --- app/admin.py | 64 +++- app/apps.py | 4 +- .../0002_rename_author_library_publication.py | 18 + ...rse_level_alter_staff_position_and_more.py | 72 ++++ ...xam_date_alter_marksheet_grade_and_more.py | 34 ++ app/migrations/0005_marksheet_subj_result.py | 19 ++ ...lter_marksheet_unique_together_and_more.py | 25 ++ ...emove_marksheet_marks_obtained_and_more.py | 46 +++ ...ove_subjectmark_marks_obtained_and_more.py | 43 +++ .../0009_alter_subjectmark_total_marks.py | 19 ++ .../0010_alter_marksheet_total_marks.py | 19 ++ .../0011_alter_marksheet_total_marks.py | 19 ++ ...alter_marksheet_obtained_marks_and_more.py | 25 ++ ...alter_marksheet_obtained_marks_and_more.py | 30 ++ app/migrations/0014_alter_marksheet_grade.py | 18 + app/migrations/0015_alter_marksheet_grade.py | 18 + app/migrations/0016_alter_marksheet_grade.py | 18 + app/models.py | 312 +++++++++++++++--- app/urls.py | 1 + app/views.py | 63 +++- assets/logo.png | Bin 24909 -> 0 bytes student_management_system/jazzmin.py | 51 +-- student_management_system/urls.py | 20 +- templates/dashboard.html | 152 +++++++++ templates/marksheet.html | 144 ++++++++ templates/marksheet/marksheet_detail.html | 77 +++++ templates/marksheet/marksheet_list.html | 26 ++ templates/staffdashboard.html | 0 templates/studentdashboard.html | 83 +++++ templates/teacherdashboard.html | 0 30 files changed, 1297 insertions(+), 123 deletions(-) create mode 100644 app/migrations/0002_rename_author_library_publication.py create mode 100644 app/migrations/0003_alter_course_level_alter_staff_position_and_more.py create mode 100644 app/migrations/0004_alter_marksheet_exam_date_alter_marksheet_grade_and_more.py create mode 100644 app/migrations/0005_marksheet_subj_result.py create mode 100644 app/migrations/0006_alter_marksheet_unique_together_and_more.py create mode 100644 app/migrations/0007_remove_marksheet_marks_obtained_and_more.py create mode 100644 app/migrations/0008_remove_subjectmark_marks_obtained_and_more.py create mode 100644 app/migrations/0009_alter_subjectmark_total_marks.py create mode 100644 app/migrations/0010_alter_marksheet_total_marks.py create mode 100644 app/migrations/0011_alter_marksheet_total_marks.py create mode 100644 app/migrations/0012_alter_marksheet_obtained_marks_and_more.py create mode 100644 app/migrations/0013_alter_marksheet_grade_alter_marksheet_obtained_marks_and_more.py create mode 100644 app/migrations/0014_alter_marksheet_grade.py create mode 100644 app/migrations/0015_alter_marksheet_grade.py create mode 100644 app/migrations/0016_alter_marksheet_grade.py delete mode 100644 assets/logo.png create mode 100644 templates/dashboard.html create mode 100644 templates/marksheet.html create mode 100644 templates/marksheet/marksheet_detail.html create mode 100644 templates/marksheet/marksheet_list.html create mode 100644 templates/staffdashboard.html create mode 100644 templates/studentdashboard.html create mode 100644 templates/teacherdashboard.html diff --git a/app/admin.py b/app/admin.py index 7024a4f..021e2f9 100644 --- a/app/admin.py +++ b/app/admin.py @@ -10,6 +10,8 @@ from app.models import ( Attendance, Result, Library, + Marksheet, + SubjectMark, ) @@ -39,6 +41,7 @@ class StudentAdmin(BaseUserAdmin): "last_name", "father_name", "mother_name", + "guardian_name", "email", "phone", "course", @@ -73,7 +76,7 @@ class StaffAdmin(BaseUserAdmin): @admin.register(Course) class CourseAdmin(admin.ModelAdmin): - list_display = ("id", "name", "course_type", "duration") + list_display = ("id", "name", "level", "course_type", "duration") search_fields = ("name",) list_filter = ("course_type",) @@ -93,7 +96,7 @@ class SubjectAdmin(admin.ModelAdmin): @admin.register(NewsEvent) class NewsEventAdmin(admin.ModelAdmin): - list_display = ("id", "title", "type", "created_at") + list_display = ("id", "title", "summary", "type", "created_at") search_fields = ("title", "summary") list_filter = ("type", "created_at") @@ -121,4 +124,59 @@ class ResultAdmin(admin.ModelAdmin): @admin.register(Library) class LibraryAdmin(admin.ModelAdmin): - list_display = ("book_name", "author", "publication_year") + list_display = ("book_name", "publication", "publication_year", "available") + + +class SubjectMarkInline(admin.TabularInline): + model = SubjectMark + extra = 1 + fields = ( + "subject", + "obtained_theory_marks", + "total_theory_marks", + "obtained_practical_marks", + "total_practical_marks", + "total_marks", + ) + readonly_fields = ("total_marks",) + + +@admin.register(Marksheet) +class MarksheetAdmin(admin.ModelAdmin): + inlines = [SubjectMarkInline] + list_display = ["student", "course", "total_marks", "obtained_marks", "grade"] + readonly_fields = ["total_marks", "obtained_marks", "grade"] + + +# class SubjectMarkInline(admin.TabularInline): +# model = SubjectMark +# extra = 1 +# fields = ( +# "subject", +# "obtained_theory_marks", +# "total_theory_marks", +# "obtained_practical_marks", +# "total_practical_marks", +# "total_marks", +# ) +# readonly_fields = ("total_marks",) +# +# +# @admin.register(Marksheet) +# class MarksheetAdmin(admin.ModelAdmin): +# list_display = ["student", "course", "total_marks", "obtained_marks", "grade"] +# list_filter = ["grade", "course"] +# search_fields = ["student__first_name", "student__last_name", "course__name"] +# readonly_fields = ["total_marks", "obtained_marks", "grade"] +# inlines = [SubjectMarkInline] +# +# fieldsets = ( +# ("Student Information", {"fields": ("student", "course")}), +# ( +# "Results", +# { +# "fields": ("total_marks", "obtained_marks", "grade"), +# "classes": ("collapse",), +# }, +# ), +# ) diff --git a/app/apps.py b/app/apps.py index ed327d2..bcfe39b 100644 --- a/app/apps.py +++ b/app/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class AppConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'app' + default_auto_field = "django.db.models.BigAutoField" + name = "app" diff --git a/app/migrations/0002_rename_author_library_publication.py b/app/migrations/0002_rename_author_library_publication.py new file mode 100644 index 0000000..acc09fe --- /dev/null +++ b/app/migrations/0002_rename_author_library_publication.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-01-08 05:51 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='library', + old_name='author', + new_name='publication', + ), + ] diff --git a/app/migrations/0003_alter_course_level_alter_staff_position_and_more.py b/app/migrations/0003_alter_course_level_alter_staff_position_and_more.py new file mode 100644 index 0000000..a5764ed --- /dev/null +++ b/app/migrations/0003_alter_course_level_alter_staff_position_and_more.py @@ -0,0 +1,72 @@ +# Generated by Django 5.1.4 on 2025-01-08 07:33 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_rename_author_library_publication'), + ] + + operations = [ + migrations.AlterField( + model_name='course', + name='level', + field=models.CharField(choices=[('basic', 'Basic'), ('primary', 'Primary'), ('secondary', 'Secondary'), ('vocational traning', 'Vocational Traning'), ('higher secondary', 'Higher Secondary'), ('graduation', 'Graduation'), ('post graduation', 'Post Graduation')], max_length=45), + ), + migrations.AlterField( + model_name='staff', + name='position', + field=models.CharField(choices=[('lab Assistant', 'Lab Assistant'), ('librarian', 'Librarian'), ('accountant', 'Accountant'), ('guard', 'Guard'), ('driver', 'Driver'), ('helper', 'Helper'), ('cleaner', 'Cleaner')], max_length=255), + ), + migrations.AlterField( + model_name='student', + name='father_occupation', + field=models.CharField(choices=[('agriculture', 'Agriculture'), ('banker', 'Banker'), ('business', 'Business'), ('doctor', 'Doctor'), ('farmer', 'Farmer'), ('fisherman', 'Fisherman'), ('public service', 'Public Service'), ('private service', 'Private Service'), ('shopkeeper', 'Shopkeeper'), ('driver', 'Driver'), ('worker', 'Worker'), ('N/A', 'N/A')], default='N/A', max_length=45), + ), + migrations.AlterField( + model_name='student', + name='mother_occupation', + field=models.CharField(choices=[('agriculture', 'Agriculture'), ('banker', 'Banker'), ('business', 'Business'), ('doctor', 'Doctor'), ('farmer', 'Farmer'), ('fisherman', 'Fisherman'), ('public service', 'Public Service'), ('private service', 'Private Service'), ('shopkeeper', 'Shopkeeper'), ('driver', 'Driver'), ('houseWife', 'HouseWife'), ('worker', 'Worker'), ('N/A', 'N/A')], default='N/A', max_length=45), + ), + migrations.AlterField( + model_name='student', + name='relationship_with_student', + field=models.CharField(choices=[('father', 'Father'), ('mother', 'Mother'), ('brother', 'Brother'), ('sister', 'Sister'), ('uncle', 'Uncle'), ('aunt', 'Aunt'), ('grandfather', 'Grandfather')], max_length=45), + ), + migrations.CreateModel( + name='Certificate', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('title', models.CharField(max_length=255)), + ('description', models.TextField(blank=True, null=True)), + ('issue_date', models.DateField(auto_now_add=True)), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.student')), + ], + options={ + 'verbose_name': 'Certificate', + 'verbose_name_plural': 'Certificate', + }, + ), + migrations.CreateModel( + name='Marksheet', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('marks_obtained', models.DecimalField(decimal_places=2, max_digits=5)), + ('total_marks', models.DecimalField(decimal_places=2, max_digits=5)), + ('grade', models.CharField(blank=True, max_length=2, null=True)), + ('exam_date', models.DateField()), + ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='marksheets', to='app.course')), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='marksheets', to='app.student')), + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='marksheets', to='app.subject')), + ], + options={ + 'verbose_name': 'Marksheet', + 'verbose_name_plural': 'Marksheets', + 'unique_together': {('student', 'course', 'subject')}, + }, + ), + ] diff --git a/app/migrations/0004_alter_marksheet_exam_date_alter_marksheet_grade_and_more.py b/app/migrations/0004_alter_marksheet_exam_date_alter_marksheet_grade_and_more.py new file mode 100644 index 0000000..10c2d23 --- /dev/null +++ b/app/migrations/0004_alter_marksheet_exam_date_alter_marksheet_grade_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.1.4 on 2025-01-08 07:39 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_alter_course_level_alter_staff_position_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='exam_date', + field=models.DateField(help_text='Date of the examination'), + ), + migrations.AlterField( + model_name='marksheet', + name='grade', + field=models.CharField(blank=True, choices=[('A+', 'Excellent'), ('A', 'Very Good'), ('B', 'Good'), ('C', 'Average'), ('D', 'Below Average'), ('F', 'Fail')], help_text='Grade assigned based on marks', max_length=2, null=True), + ), + migrations.AlterField( + model_name='marksheet', + name='marks_obtained', + field=models.PositiveIntegerField(help_text='Marks obtained by the student', validators=[django.core.validators.MinValueValidator(0)]), + ), + migrations.AlterField( + model_name='marksheet', + name='total_marks', + field=models.PositiveIntegerField(help_text='Total marks for the subject', validators=[django.core.validators.MinValueValidator(1)]), + ), + ] diff --git a/app/migrations/0005_marksheet_subj_result.py b/app/migrations/0005_marksheet_subj_result.py new file mode 100644 index 0000000..2070da6 --- /dev/null +++ b/app/migrations/0005_marksheet_subj_result.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2025-01-08 07:51 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0004_alter_marksheet_exam_date_alter_marksheet_grade_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='marksheet', + name='subj_result', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='app.result'), + ), + ] diff --git a/app/migrations/0006_alter_marksheet_unique_together_and_more.py b/app/migrations/0006_alter_marksheet_unique_together_and_more.py new file mode 100644 index 0000000..06c3e73 --- /dev/null +++ b/app/migrations/0006_alter_marksheet_unique_together_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.4 on 2025-01-08 07:53 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0005_marksheet_subj_result'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='marksheet', + unique_together={('student', 'course')}, + ), + migrations.RemoveField( + model_name='marksheet', + name='exam_date', + ), + migrations.RemoveField( + model_name='marksheet', + name='subject', + ), + ] diff --git a/app/migrations/0007_remove_marksheet_marks_obtained_and_more.py b/app/migrations/0007_remove_marksheet_marks_obtained_and_more.py new file mode 100644 index 0000000..44828dd --- /dev/null +++ b/app/migrations/0007_remove_marksheet_marks_obtained_and_more.py @@ -0,0 +1,46 @@ +# Generated by Django 5.1.4 on 2025-01-08 08:29 + +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0006_alter_marksheet_unique_together_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='marksheet', + name='marks_obtained', + ), + migrations.RemoveField( + model_name='marksheet', + name='subj_result', + ), + migrations.AddField( + model_name='marksheet', + name='obtained_marks', + field=models.PositiveIntegerField(default=0, help_text='Total marks obtained by the student', validators=[django.core.validators.MinValueValidator(0)]), + ), + migrations.AlterField( + model_name='marksheet', + name='total_marks', + field=models.PositiveIntegerField(help_text='Total marks for the course', validators=[django.core.validators.MinValueValidator(1)]), + ), + migrations.CreateModel( + name='SubjectMark', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('marks_obtained', models.PositiveIntegerField(help_text='Marks obtained in this subject', validators=[django.core.validators.MinValueValidator(0)])), + ('total_marks', models.PositiveIntegerField(help_text='Total marks for this subject', validators=[django.core.validators.MinValueValidator(1)])), + ('marksheet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subject_marks', to='app.marksheet')), + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subject_marks', to='app.subject')), + ], + options={ + 'unique_together': {('marksheet', 'subject')}, + }, + ), + ] diff --git a/app/migrations/0008_remove_subjectmark_marks_obtained_and_more.py b/app/migrations/0008_remove_subjectmark_marks_obtained_and_more.py new file mode 100644 index 0000000..92da922 --- /dev/null +++ b/app/migrations/0008_remove_subjectmark_marks_obtained_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.1.4 on 2025-01-08 08:42 + +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0007_remove_marksheet_marks_obtained_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='subjectmark', + name='marks_obtained', + ), + migrations.AddField( + model_name='subjectmark', + name='obtained_practical_marks', + field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5), + ), + migrations.AddField( + model_name='subjectmark', + name='obtained_theory_marks', + field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5), + ), + migrations.AddField( + model_name='subjectmark', + name='total_practical_marks', + field=models.DecimalField(decimal_places=2, default=Decimal('20.00'), max_digits=5), + ), + migrations.AddField( + model_name='subjectmark', + name='total_theory_marks', + field=models.DecimalField(decimal_places=2, default=Decimal('80.00'), max_digits=5), + ), + migrations.AlterField( + model_name='subjectmark', + name='total_marks', + field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), editable=False, max_digits=5), + ), + ] diff --git a/app/migrations/0009_alter_subjectmark_total_marks.py b/app/migrations/0009_alter_subjectmark_total_marks.py new file mode 100644 index 0000000..7fdd8dd --- /dev/null +++ b/app/migrations/0009_alter_subjectmark_total_marks.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2025-01-08 08:43 + +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0008_remove_subjectmark_marks_obtained_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='subjectmark', + name='total_marks', + field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5), + ), + ] diff --git a/app/migrations/0010_alter_marksheet_total_marks.py b/app/migrations/0010_alter_marksheet_total_marks.py new file mode 100644 index 0000000..fd35e53 --- /dev/null +++ b/app/migrations/0010_alter_marksheet_total_marks.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2025-01-08 08:51 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0009_alter_subjectmark_total_marks'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='total_marks', + field=models.PositiveIntegerField(default=0, help_text='Total marks for the course', validators=[django.core.validators.MinValueValidator(1)]), + ), + ] diff --git a/app/migrations/0011_alter_marksheet_total_marks.py b/app/migrations/0011_alter_marksheet_total_marks.py new file mode 100644 index 0000000..9e06813 --- /dev/null +++ b/app/migrations/0011_alter_marksheet_total_marks.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.4 on 2025-01-08 08:53 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0010_alter_marksheet_total_marks'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='total_marks', + field=models.PositiveIntegerField(help_text='Total marks for the course', validators=[django.core.validators.MinValueValidator(1)]), + ), + ] diff --git a/app/migrations/0012_alter_marksheet_obtained_marks_and_more.py b/app/migrations/0012_alter_marksheet_obtained_marks_and_more.py new file mode 100644 index 0000000..f29fc08 --- /dev/null +++ b/app/migrations/0012_alter_marksheet_obtained_marks_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.1.4 on 2025-01-08 09:01 + +import django.core.validators +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0011_alter_marksheet_total_marks'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='obtained_marks', + field=models.PositiveIntegerField(default=Decimal('0.00'), editable=False, help_text='Total marks obtained by the student', validators=[django.core.validators.MinValueValidator(0)]), + ), + migrations.AlterField( + model_name='marksheet', + name='total_marks', + field=models.PositiveIntegerField(default=Decimal('0.00'), editable=False, help_text='Total marks for the course', validators=[django.core.validators.MinValueValidator(1)]), + ), + ] diff --git a/app/migrations/0013_alter_marksheet_grade_alter_marksheet_obtained_marks_and_more.py b/app/migrations/0013_alter_marksheet_grade_alter_marksheet_obtained_marks_and_more.py new file mode 100644 index 0000000..7e56ed3 --- /dev/null +++ b/app/migrations/0013_alter_marksheet_grade_alter_marksheet_obtained_marks_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.4 on 2025-01-08 09:16 + +import django.core.validators +from decimal import Decimal +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0012_alter_marksheet_obtained_marks_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='grade', + field=models.CharField(blank=True, choices=[('A+', 'Excellent'), ('A', 'Very Good'), ('B+', 'Good'), ('B', ''), ('C+', 'Average'), ('C', ''), ('D+', 'Below Average'), ('D', 'Fail')], help_text='Grade assigned based on marks', max_length=2, null=True), + ), + migrations.AlterField( + model_name='marksheet', + name='obtained_marks', + field=models.PositiveIntegerField(default=Decimal('0.00'), help_text='Total marks obtained by the student', validators=[django.core.validators.MinValueValidator(0)]), + ), + migrations.AlterField( + model_name='marksheet', + name='total_marks', + field=models.PositiveIntegerField(default=Decimal('0.00'), help_text='Total marks for the course', validators=[django.core.validators.MinValueValidator(1)]), + ), + ] diff --git a/app/migrations/0014_alter_marksheet_grade.py b/app/migrations/0014_alter_marksheet_grade.py new file mode 100644 index 0000000..ee9f117 --- /dev/null +++ b/app/migrations/0014_alter_marksheet_grade.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-01-08 09:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0013_alter_marksheet_grade_alter_marksheet_obtained_marks_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='grade', + field=models.CharField(blank=True, choices=[('A+', 'Excellent'), ('A', 'Very Good'), ('B+', 'Good'), ('B', 'G'), ('C+', 'Average'), ('C', 'A'), ('D+', 'Below Average'), ('D', 'Fail')], help_text='Grade assigned based on marks', max_length=2, null=True), + ), + ] diff --git a/app/migrations/0015_alter_marksheet_grade.py b/app/migrations/0015_alter_marksheet_grade.py new file mode 100644 index 0000000..457b02d --- /dev/null +++ b/app/migrations/0015_alter_marksheet_grade.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-01-08 09:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0014_alter_marksheet_grade'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='grade', + field=models.CharField(blank=True, choices=[('A+', 'Outstanding'), ('A', 'Excellent'), ('B+', 'Very Good'), ('B', 'Good'), ('C+', 'Above Average'), ('C', 'Average'), ('D', 'Below Average'), ('E', 'Fail')], help_text='Grade assigned based on marks', max_length=2, null=True), + ), + ] diff --git a/app/migrations/0016_alter_marksheet_grade.py b/app/migrations/0016_alter_marksheet_grade.py new file mode 100644 index 0000000..fcb4e99 --- /dev/null +++ b/app/migrations/0016_alter_marksheet_grade.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.4 on 2025-01-08 09:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0015_alter_marksheet_grade'), + ] + + operations = [ + migrations.AlterField( + model_name='marksheet', + name='grade', + field=models.CharField(blank=True, choices=[('A+', 'A+'), ('A', 'A'), ('B+', 'B+'), ('B', 'B'), ('C+', 'C+'), ('C', 'C'), ('D', 'D'), ('E', 'E')], help_text='Grade assigned based on marks', max_length=2, null=True), + ), + ] diff --git a/app/models.py b/app/models.py index 8fa8550..142a763 100644 --- a/app/models.py +++ b/app/models.py @@ -5,6 +5,7 @@ from django.contrib.auth.hashers import make_password import random from decimal import Decimal from datetime import time, timedelta, datetime +from django.core.validators import MinValueValidator, MaxValueValidator # Abstract BaseUser Model @@ -19,7 +20,6 @@ class BaseUser(models.Model): ) username = models.CharField(max_length=255, unique=True, blank=True, null=True) password = models.CharField(max_length=255) - address = models.CharField(max_length=255) group = models.ManyToManyField(Group, blank=True) user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True) @@ -42,7 +42,6 @@ class BaseUser(models.Model): email=self.email, first_name=self.first_name, last_name=self.last_name, - address=self.address, is_staff=True, is_active=self.active_status, password=self.password, @@ -52,7 +51,6 @@ class BaseUser(models.Model): self.user.email = self.email self.user.first_name = self.first_name self.user.last_name = self.last_name - self.user.address = self.address self.user.is_active = self.active_status self.user.is_staff = True self.user.password = self.password @@ -103,6 +101,7 @@ class Teacher(BaseUser): class Student(BaseUser): date_of_birth = models.DateField(blank=False) gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")]) + address = models.CharField(max_length=255) course = models.ForeignKey("Course", on_delete=models.CASCADE) academic_year = models.ForeignKey("AcademicYear", on_delete=models.CASCADE) father_name = models.CharField(max_length=100) @@ -111,17 +110,17 @@ class Student(BaseUser): ) father_email = models.EmailField(blank=True, null=True) father_occupation_choice = ( - ("Agriculture", "Agriculture"), - ("Banker", "Banker"), - ("Business", "Business"), - ("Doctor", "Doctor"), - ("Farmer", "Farmer"), - ("Fisherman", "Fisherman"), - ("Public Service", "Public Service"), - ("Private Service", "Private Service"), - ("Shopkeeper", "Shopkeeper"), - ("Driver", "Driver"), - ("Worker", "Worker"), + ("agriculture", "Agriculture"), + ("banker", "Banker"), + ("business", "Business"), + ("doctor", "Doctor"), + ("farmer", "Farmer"), + ("fisherman", "Fisherman"), + ("public service", "Public Service"), + ("private service", "Private Service"), + ("shopkeeper", "Shopkeeper"), + ("driver", "Driver"), + ("worker", "Worker"), ("N/A", "N/A"), ) father_occupation = models.CharField( @@ -136,18 +135,18 @@ class Student(BaseUser): ) mother_email = models.EmailField(blank=True, null=True) mother_occupation_choice = ( - ("Agriculture", "Agriculture"), - ("Banker", "Banker"), - ("Business", "Business"), - ("Doctor", "Doctor"), - ("Farmer", "Farmer"), - ("Fisherman", "Fisherman"), - ("Public Service", "Public Service"), - ("Private Service", "Private Service"), - ("Shopkeeper", "Shopkeeper"), - ("Driver", "Driver"), - ("HouseWife", "HouseWife"), - ("Worker", "Worker"), + ("agriculture", "Agriculture"), + ("banker", "Banker"), + ("business", "Business"), + ("doctor", "Doctor"), + ("farmer", "Farmer"), + ("fisherman", "Fisherman"), + ("public service", "Public Service"), + ("private service", "Private Service"), + ("shopkeeper", "Shopkeeper"), + ("driver", "Driver"), + ("houseWife", "HouseWife"), + ("worker", "Worker"), ("N/A", "N/A"), ) mother_occupation = models.CharField( @@ -159,13 +158,13 @@ class Student(BaseUser): ) guardian_email = models.EmailField(blank=True, null=True) relationship_choice = ( - ("Father", "Father"), - ("Mother", "Mother"), - ("Brother", "Brother"), - ("Sister", "Sister"), - ("Uncle", "Uncle"), - ("Aunt", "Aunt"), - ("Grandfather", "Grandfather"), + ("father", "Father"), + ("mother", "Mother"), + ("brother", "Brother"), + ("sister", "Sister"), + ("uncle", "Uncle"), + ("aunt", "Aunt"), + ("grandfather", "Grandfather"), ) relationship_with_student = models.CharField( choices=relationship_choice, max_length=45 @@ -182,13 +181,13 @@ class Staff(BaseUser): position = models.CharField( max_length=255, choices=[ - ("Lab Assistant", "Lab Assistant"), - ("Librarian", "Librarian"), - ("Accountant", "Accountant"), - ("Guard", "Guard"), - ("Driver", "Driver"), - ("Helper", "Helper"), - ("Cleaner", "Cleaner"), + ("lab Assistant", "Lab Assistant"), + ("librarian", "Librarian"), + ("accountant", "Accountant"), + ("guard", "Guard"), + ("driver", "Driver"), + ("helper", "Helper"), + ("cleaner", "Cleaner"), ], ) address = models.CharField(max_length=255) @@ -211,13 +210,13 @@ class Staff(BaseUser): class Course(models.Model): name = models.CharField(max_length=100) level_choice = ( - ("Basic", "Basic"), - ("Primary", "Primary"), - ("Secondary", "Secondary"), - ("Vocational Traning", "Vocational Traning"), - ("Higher Secondary", "Higher Secondary"), - ("Graduation", "Graduation"), - ("Post Graduation", "Post Graduation"), + ("basic", "Basic"), + ("primary", "Primary"), + ("secondary", "Secondary"), + ("vocational traning", "Vocational Traning"), + ("higher secondary", "Higher Secondary"), + ("graduation", "Graduation"), + ("post graduation", "Post Graduation"), ) level = models.CharField(choices=level_choice, max_length=45) course_type = models.CharField( @@ -239,7 +238,7 @@ class AcademicYear(models.Model): year = models.CharField(max_length=255, unique=True) def __str__(self): - return self.batch + return self.year class Meta: verbose_name = "Academic Year" @@ -271,15 +270,17 @@ class Subject(models.Model): # NewsEvent model class NewsEvent(models.Model): + class NewsEventType(models.TextChoices): + NEWS = "news", "News" + EVENT = "event", "Event" + title = models.CharField(max_length=255) summary = models.TextField() - type = models.CharField( - max_length=5, choices=[("news", "News"), ("event", "Event")] - ) + type = models.CharField(max_length=5, choices=NewsEventType.choices) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"{self.title} ({self.get_type_display()})" + return f"{self.title} ({self.type})" class Meta: verbose_name = "News/Event" @@ -337,7 +338,7 @@ class Library(models.Model): category = models.CharField(max_length=255) book_name = models.CharField(max_length=255) description = models.CharField(max_length=255) - author = models.CharField(max_length=255) + publication = models.CharField(max_length=255) publication_year = models.PositiveIntegerField() quantity = models.PositiveIntegerField() available = models.CharField(max_length=1, choices=[("Y", "Yes"), ("N", "NO")]) @@ -349,3 +350,208 @@ class Library(models.Model): class Meta: verbose_name = "Library" verbose_name_plural = "Library" + + +class Certificate(models.Model): + id = models.AutoField(primary_key=True) + student = models.ForeignKey("Student", on_delete=models.CASCADE) + course = models.ForeignKey("Course", on_delete=models.CASCADE) + title = models.CharField(max_length=255) + description = models.TextField(blank=True, null=True) + issue_date = models.DateField(auto_now_add=True) + + class Meta: + verbose_name = "Certificate" + verbose_name_plural = "Certificate" + + +class Marksheet(models.Model): + student = models.ForeignKey( + "Student", on_delete=models.CASCADE, related_name="marksheets" + ) + course = models.ForeignKey( + "Course", on_delete=models.CASCADE, related_name="marksheets" + ) + total_marks = models.PositiveIntegerField( + validators=[MinValueValidator(1)], + default=Decimal("0.00"), + help_text="Total marks for the course", + # editable=False, + ) + obtained_marks = models.PositiveIntegerField( + validators=[MinValueValidator(0)], + default=Decimal("0.00"), + help_text="Total marks obtained by the student", + # editable=False, + ) + grade = models.CharField( + max_length=2, + choices=[ + # ("A+", "Outstanding"), + # ("A", "Excellent"), + # ("B+", "Very Good"), + # ("B", "Good"), + # ("C+", "Above Average"), + # ("C", "Average"), + # ("D", "Below Average"), + # ("E", "Fail"), + ("A+", "A+"), + ("A", "A"), + ("B+", "B+"), + ("B", "B"), + ("C+", "C+"), + ("C", "C"), + ("D", "D"), + ("E", "E"), + ], + blank=True, + null=True, + help_text="Grade assigned based on marks", + ) + + class Meta: + verbose_name = "Marksheet" + verbose_name_plural = "Marksheets" + unique_together = ("student", "course") + + def calculate_totals(self): + subject_marks = self.subject_marks.all() + + total_marks = sum( + subject.total_practical_marks + subject.total_theory_marks + for subject in subject_marks + ) or Decimal("0.00") + + obtained_marks = sum( + subject.obtained_practical_marks + subject.obtained_theory_marks + for subject in subject_marks + ) or Decimal("0.00") + + return total_marks, obtained_marks + + def save(self, *args, **kwargs): + if not self.pk: + super().save(*args, **kwargs) + + self.total_marks, self.obtained_marks = self.calculate_totals() + + if self.total_marks > 0: + percentage = (self.obtained_marks / self.total_marks) * 100 + if percentage >= 90: + self.grade = "A+" + elif percentage >= 80: + self.grade = "A" + elif percentage >= 70: + self.grade = "B+" + elif percentage >= 60: + self.grade = "B" + elif percentage >= 50: + self.grade = "C+" + elif percentage >= 40: + self.grade = "C" + elif percentage >= 30: + self.grade = "D" + else: + self.grade = "E" + + super().save(*args, **kwargs) + + def __str__(self): + return f"Marksheet: {self.student.first_name} {self.student.last_name}" + + +class SubjectMark(models.Model): + marksheet = models.ForeignKey( + Marksheet, on_delete=models.CASCADE, related_name="subject_marks" + ) + subject = models.ForeignKey( + "Subject", on_delete=models.CASCADE, related_name="subject_marks" + ) + obtained_practical_marks = models.DecimalField( + max_digits=5, decimal_places=2, default=Decimal("0.00") + ) + total_practical_marks = models.DecimalField( + max_digits=5, decimal_places=2, default=Decimal("20.00") + ) + obtained_theory_marks = models.DecimalField( + max_digits=5, decimal_places=2, default=Decimal("0.00") + ) + total_theory_marks = models.DecimalField( + max_digits=5, decimal_places=2, default=Decimal("80.00") + ) + total_marks = models.DecimalField( + max_digits=5, decimal_places=2, default=Decimal("0.00") + ) + + def get_total(self): + return Decimal(str(self.obtained_practical_marks or 0)) + Decimal( + str(self.obtained_theory_marks or 0) + ) + + def save(self, *args, **kwargs): + self.total_marks = self.get_total() + super().save(*args, **kwargs) + self.marksheet.save() + + class Meta: + unique_together = ("marksheet", "subject") + + def __str__(self): + return f"{self.subject.name} - {self.marksheet.student.first_name}" + + +# class Marksheet(models.Model): +# student = models.ForeignKey( +# "Student", on_delete=models.CASCADE, related_name="marksheets" +# ) +# course = models.ForeignKey( +# "Course", on_delete=models.CASCADE, related_name="marksheets" +# ) +# subj_result = models.ForeignKey( +# "Result", on_delete=models.CASCADE, related_name="marksheets", null=True +# ) +# marks_obtained = models.PositiveIntegerField( +# validators=[MinValueValidator(0)], help_text="Marks obtained by the student" +# ) +# total_marks = models.PositiveIntegerField( +# validators=[MinValueValidator(1)], help_text="Total marks for the subject" +# ) +# grade = models.CharField( +# max_length=2, +# choices=[ +# ("A+", "Excellent"), +# ("A", "Very Good"), +# ("B", "Good"), +# ("C", "Average"), +# ("D", "Below Average"), +# ("F", "Fail"), +# ], +# blank=True, +# null=True, +# help_text="Grade assigned based on marks", +# ) +# +# class Meta: +# verbose_name = "Marksheet" +# verbose_name_plural = "Marksheets" +# unique_together = ("student", "course") +# +# def save(self, *args, **kwargs): +# percentage = (self.marks_obtained / self.total_marks) * 100 +# if percentage >= 90: +# self.grade = "A+" +# elif percentage >= 80: +# self.grade = "A" +# elif percentage >= 70: +# self.grade = "B" +# elif percentage >= 60: +# self.grade = "C" +# elif percentage >= 50: +# self.grade = "D" +# else: +# self.grade = "F" +# +# super().save(*args, **kwargs) +# +# def __str__(self): +# return f"Marksheet: {self.student.first_name} {self.student.last_name}" diff --git a/app/urls.py b/app/urls.py index 8f80018..3787e52 100644 --- a/app/urls.py +++ b/app/urls.py @@ -4,4 +4,5 @@ from app import views urlpatterns = [ path("dashboard", views.dashboard, name="dashboard"), path("studentdashboard", views.studentdashboard, name="studentdashboard"), + path("marksheet", views.marksheet, name="marksheet"), ] diff --git a/app/views.py b/app/views.py index 50ad71f..f5c291d 100644 --- a/app/views.py +++ b/app/views.py @@ -1,32 +1,71 @@ -from django.shortcuts import render, get_object_or_404 +from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.admin import site -from app.models import Student, NewsEvent, Subject - +from app.models import ( + Student, + NewsEvent, + AcademicYear, + Subject, + Teacher, + Staff, + Course, + Marksheet, + SubjectMark, +) from django.contrib.auth.decorators import login_required -# Dashboard for admin -@login_required(login_url="login") +@login_required def dashboard(request): + if not request.user.is_superuser: + return redirect("studentdashboard") + + total_student = Student.objects.count() + total_teacher = Teacher.objects.count() + total_staff = Staff.objects.count() + total_course = Course.objects.count() + total_subjects = Subject.objects.count() context = { **site.each_context(request), "title": "Dashboard", # "breadcrumbs": [{"name": "Dashboard", "url": "dashboard"}], + "total_student": total_student, + "total_teacher": total_teacher, + "total_staff": total_staff, + "total_course": total_course, + "total_subjects": total_subjects, } return render(request, "dashboard.html", context) -# Student-specific dashboard +@login_required def studentdashboard(request): + total_subject = Subject.objects.count() + student = Student.objects.get(user=request.user) + course_name = student.course.name + course = student.course + total_subjects = Subject.objects.filter(course=course).count() + subject = Subject.objects.filter(course=course) + context = { **site.each_context(request), "title": "Student Dashboard", + "total_subject": total_subject, + "course_name": course_name, + "subject": subject, + "total_subjects": total_subjects, } - if request.user.is_authenticated: - news_events = NewsEvent.objects.all() - subjects = Subject.objects.all() - context["news_events"] = news_events - context["subjects"] = subjects - return render(request, "studentdashboard.html", context) + + +def marksheet(request): + marksheet = Marksheet.objects.first() + subject = SubjectMark.objects.filter(marksheet=marksheet) + context = { + **site.each_context(request), + "title": "Marksheet", + "marksheet": marksheet, + "subject": subject, + } + + return render(request, "marksheet.html", context) diff --git a/assets/logo.png b/assets/logo.png deleted file mode 100644 index 05e9f6976ba2a39068040959439e70bf8a9899c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24909 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 Zy>_xH#qk~j-;aQx@LE;2O4>B^zW@o!n1lcT diff --git a/student_management_system/jazzmin.py b/student_management_system/jazzmin.py index 44e8f4a..7e059df 100644 --- a/student_management_system/jazzmin.py +++ b/student_management_system/jazzmin.py @@ -1,63 +1,43 @@ - JAZZMIN_SETTINGS = { # title of the window (Will default to current_admin_site.site_title if absent or None) "site_title": "Student Management System Admin", - # Title on the login screen (19 chars max) (defaults to current_admin_site.site_header if absent or None) "site_header": "Student Management System", - # Title on the brand (19 chars max) (defaults to current_admin_site.site_header if absent or None) "site_brand": "SMS", - # Logo to use for your site, must be present in static files, used for brand on top left "site_logo": "/src/img/logo2.png", - # CSS classes that are applied to the logo above "site_logo_classes": "img-circle", - # Relative path to a favicon for your site, will default to site_logo if absent (ideally 32x32 px) "site_icon": None, - # Welcome text on the login screen "welcome_sign": "Welcome to the Student Management System", - # Copyright on the footer "copyright": "ADDRSSS", - # theme - - - # The model admin to search from the search bar, search bar omitted if excluded # "search_model": "auth.User", - # Field name on user model that contains avatar ImageField/URLField/Charfield or a callable that receives the user "user_avatar": None, - + "version": False, ############ # Top Menu # ############ - # Links to put along the top menu "topmenu_links": [ - # Url that gets reversed (Permissions can be added) - {"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]}, - + {"name": "Home", "url": "admin:index", "permissions": ["auth.view_user"]}, # external url that opens in a new window (Permissions can be added) # {"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True}, - # model admin to link to (Permissions checked against model) # {"model": "auth.User"}, - # App with dropdown menu to all its models pages (Permissions checked against models) # {"app": "books"}, ], - ############# # User Menu # ############# - # Additional links to include in the user menu on the top right ("app" url type is not allowed) # "usermenu_links": [ # {"name": "Support", "url": "https://github.com/farridav/django-jazzmin/issues", "new_window": True}, @@ -67,30 +47,30 @@ JAZZMIN_SETTINGS = { ############# # Side Menu # ############# - # Whether to display the side menu "show_sidebar": True, - # Whether to aut expand the menu "navigation_expanded": True, - # Hide these apps when generating side menu e.g (auth) "hide_apps": [], - # Hide these models when generating side menu (e.g auth.user) "hide_models": [], - # List of apps (and/or models) to base side menu ordering off of (does not need to contain all apps/models) - "order_with_respect_to": ["auth"], - - - + "order_with_respect_to": ["auth", "dashboard"], + "custom_links": { + "app": [ + { + "name": "Dashboard", + "url": "dashboard", + "icon": "fas fa-tachometer-alt", + }, + ] + }, ################# # Related Modal # ################# # Use modals instead of popups "related_modal_active": False, - ############# # UI Tweaks # ############# @@ -99,7 +79,6 @@ JAZZMIN_SETTINGS = { "custom_js": None, # Whether to show the UI customizer on the sidebar "show_ui_builder": False, - ############### # Change view # ############### @@ -111,10 +90,14 @@ JAZZMIN_SETTINGS = { # - carousel "changeform_format": "horizontal_tabs", # override change forms on a per modeladmin basis - "changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs"}, + "changeform_format_overrides": { + "auth.user": "horizontal_tabs", + "auth.group": "vertical_tabs", + }, } JAZZMIN_UI_TWEAKS = { + # "footer_fixed": True, "theme": "lumen", # "dark_mode_theme": "cyborg", } diff --git a/student_management_system/urls.py b/student_management_system/urls.py index 439e79c..8782abe 100644 --- a/student_management_system/urls.py +++ b/student_management_system/urls.py @@ -25,26 +25,8 @@ urlpatterns = ( [ path("admin/", admin.site.urls), path("", RedirectView.as_view(url="/admin/", permanent=False)), + path("", include("app.urls")), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ) -# from django.contrib import admin -# from django.urls import path, include -# -# from django.conf import settings -# from django.conf.urls.static import static -# -# -# urlpatterns = ( -# [ -# path("admin/", admin.site.urls), -# path( -# "", -# include("app.urls"), -# ), -# ] -# + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -# + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -# ) - diff --git a/templates/dashboard.html b/templates/dashboard.html new file mode 100644 index 0000000..356dc7a --- /dev/null +++ b/templates/dashboard.html @@ -0,0 +1,152 @@ +{% extends "admin/base_site.html" %} +{% block content %} +

{{ title }}

+

Welcome

+ + +
+
+
+
Total Students
+

{{ total_student }}

+
+
+
+
+
Total Staff
+

{{ total_staff }}

+
+
+
+
+
Total Teacher
+

{{ total_teacher }}

+
+
+
+
+
Total Course
+

{{ total_course }}

+
+
+
+
+
Total Subject
+

{{ total_subjects }}

+
+
+
+
+
+ + + + + + + + + + + + + +
S.NNameCourseAcademic Year
1helloDCOM2023
+
+
+ + + + + + + + + + + + + + + + + +
S.NNameAddressPhone NoGmail
1hellokohalpur9801230123example@example.com
+
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +{% endblock %} diff --git a/templates/marksheet.html b/templates/marksheet.html new file mode 100644 index 0000000..ee0a879 --- /dev/null +++ b/templates/marksheet.html @@ -0,0 +1,144 @@ +{% extends 'admin/base_site.html' %} +{% block content %} + + + +
+
+

SHREE SECONDARY SCHOOL

+

Kohalpur-5, Banke

+

Estd. 281 BS

+

GRADE - SHEET

+
+
+

+ The grade(s) secured by: {{ marksheet.student.first_name }} {{ marksheet.student.first_name }} +

+

+ Date of Birth: + {{ marksheet.student.date_of_birth }} +

+ In the Examination, {{ marksheet.student.course.name }} of {{ marksheet.student.academic_year }} are given below: +

+
+ + + + + + + + + + + + {% for subject in marksheet.subject_marks.all %} + + + + + + + + {% endfor %} + + + + + +
S.N.SubjectTheoryPracticalTotal
{{ forloop.counter }}{{ subject.subject.name }}{{ subject.obtained_theory_marks }}{{ subject.obtained_practical_marks }}{{ subject.total_marks }}
Grand Total{{ marksheet.obtained_marks }}
+
+

+ Total Grade Point Average (Grade): {{ marksheet.grade }} +

+

+ Percentage: + {{ marksheet.obtained_marks|div:marksheet.total_marks|mul:100|floatformat:2 }}% +

+
+ Print PDF +
+ +{% endblock %} diff --git a/templates/marksheet/marksheet_detail.html b/templates/marksheet/marksheet_detail.html new file mode 100644 index 0000000..689e1c2 --- /dev/null +++ b/templates/marksheet/marksheet_detail.html @@ -0,0 +1,77 @@ + +{% extends "admin/base_site.html" %} +{% block content %} +
+
+ +
+

Student Marksheet

+
+
+

Student Name:

+

{{ marksheet.student.first_name }} {{ marksheet.student.last_name }}

+
+
+

Course:

+

{{ marksheet.course.name }}

+
+
+
+ +
+ + + + + + + + + + + {% for subject_mark in marksheet.subject_marks.all %} + + + + + + + {% endfor %} + + + + + + + + +
SubjectTheory MarksPractical MarksTotal
{{ subject_mark.subject.name }} + {{ subject_mark.obtained_theory_marks }}/{{ + subject_mark.total_theory_marks }} + + {{ subject_mark.obtained_practical_marks }}/{{ + subject_mark.total_practical_marks }} + + {{ subject_mark.total_marks }}/{{ + subject_mark.total_practical_marks|add:subject_mark.total_theory_marks + }} +
Total{{ marksheet.obtained_marks }}/{{ marksheet.total_marks }}
+
+ +
+
+

+ Final Grade: + + {{ marksheet.grade }} + +

+ + + +
+
+
+ Print PDF +
+{% endblock %} diff --git a/templates/marksheet/marksheet_list.html b/templates/marksheet/marksheet_list.html new file mode 100644 index 0000000..d9bbf64 --- /dev/null +++ b/templates/marksheet/marksheet_list.html @@ -0,0 +1,26 @@ +{% extends "admin/base_site.html" %} +{% block content %} +
+

Student Marksheets

+
+ {% for marksheet in marksheets %} +
+

{{ marksheet.student.first_name }} {{ marksheet.student.last_name }}

+

Course: {{ marksheet.course.name }}

+
+ + Grade: + + {{ marksheet.grade }} + + + View Details +
+
+ {% empty %} +
No marksheets available.
+ {% endfor %} +
+
+{% endblock %} diff --git a/templates/staffdashboard.html b/templates/staffdashboard.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/studentdashboard.html b/templates/studentdashboard.html new file mode 100644 index 0000000..f28d9a9 --- /dev/null +++ b/templates/studentdashboard.html @@ -0,0 +1,83 @@ +{% extends "admin/base_site.html" %} +{% block content %} +

{{ title }}

+

Welcome

+ +
+
+
+
Course
+

{{ course_name }}

+
+
+
+
+
Current Semester
+

5

+
+
+
+
+
Total Subject
+

{{ total_subjects }}

+
+
+
+ +
+ +
+ +
+ +
+ +{% endblock %} diff --git a/templates/teacherdashboard.html b/templates/teacherdashboard.html new file mode 100644 index 0000000..e69de29