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 05e9f69..0000000 Binary files a/assets/logo.png and /dev/null differ 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