diff --git a/app/admin.py b/app/admin.py index 0d4b096..7024a4f 100644 --- a/app/admin.py +++ b/app/admin.py @@ -9,20 +9,19 @@ from app.models import ( NewsEvent, Attendance, Result, + Library, ) +# BaseUserAdmin to handle common behavior for BaseUser-based models class BaseUserAdmin(admin.ModelAdmin): exclude = ["user"] def save_model(self, request, obj, form, change): - # Save the BaseUser instance super().save_model(request, obj, form, change) - - # Sync the groups with the related User object if obj.user: - obj.user.groups.set(obj.group.all()) # Sync groups - obj.user.save() # Save the User object to persist changes + obj.user.groups.set(obj.group.all()) + obj.user.save() @admin.register(Teacher) @@ -38,13 +37,22 @@ class StudentAdmin(BaseUserAdmin): "id", "first_name", "last_name", + "father_name", + "mother_name", "email", "phone", "course", "academic_year", "active_status", ) - search_fields = ("first_name", "last_name", "email", "phone") + search_fields = ( + "first_name", + "last_name", + "father_name", + "mother_name", + "email", + "phone", + ) list_filter = ("active_status", "gender", "course", "academic_year") @@ -72,8 +80,8 @@ class CourseAdmin(admin.ModelAdmin): @admin.register(AcademicYear) class AcademicYearAdmin(admin.ModelAdmin): - list_display = ("id", "batch") - search_fields = ("batch",) + list_display = ("id", "year") + search_fields = ("year",) @admin.register(Subject) @@ -109,3 +117,8 @@ class ResultAdmin(admin.ModelAdmin): def save_model(self, request, obj, form, change): obj.total_marks = obj.get_total() super().save_model(request, obj, form, change) + + +@admin.register(Library) +class LibraryAdmin(admin.ModelAdmin): + list_display = ("book_name", "author", "publication_year") diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py index 3995cc4..bb2b37c 100644 --- a/app/migrations/0001_initial.py +++ b/app/migrations/0001_initial.py @@ -1,7 +1,9 @@ -# Generated by Django 5.1.4 on 2025-01-06 17:31 +# Generated by Django 5.1.4 on 2025-01-07 17:39 import django.core.validators import django.db.models.deletion +from decimal import Decimal +from django.conf import settings from django.db import migrations, models @@ -11,6 +13,7 @@ class Migration(migrations.Migration): dependencies = [ ('auth', '0012_alter_user_first_name_max_length'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ @@ -18,17 +21,44 @@ class Migration(migrations.Migration): name='AcademicYear', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('batch', models.CharField(max_length=255, unique=True)), + ('year', models.CharField(max_length=255, unique=True)), ], + options={ + 'verbose_name': 'Academic Year', + 'verbose_name_plural': 'Academic Years', + }, ), migrations.CreateModel( name='Course', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), + ('level', 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)), ('course_type', models.CharField(choices=[('semester', 'Semester-based'), ('yearly', 'Yearly-based')], max_length=10)), ('duration', models.PositiveIntegerField()), ], + options={ + 'verbose_name': 'Course', + 'verbose_name_plural': 'Courses', + }, + ), + migrations.CreateModel( + name='Library', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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_year', models.PositiveIntegerField()), + ('quantity', models.PositiveIntegerField()), + ('available', models.CharField(choices=[('Y', 'Yes'), ('N', 'NO')], max_length=1)), + ('date_added', models.DateField(auto_now_add=True)), + ], + options={ + 'verbose_name': 'Library', + 'verbose_name_plural': 'Library', + }, ), migrations.CreateModel( name='NewsEvent', @@ -39,44 +69,71 @@ class Migration(migrations.Migration): ('type', models.CharField(choices=[('news', 'News'), ('event', 'Event')], max_length=5)), ('created_at', models.DateTimeField(auto_now_add=True)), ], + options={ + 'verbose_name': 'News/Event', + 'verbose_name_plural': 'News/Events', + }, ), migrations.CreateModel( name='Staff', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active_status', models.BooleanField(default=True)), ('first_name', models.CharField(max_length=255)), + ('middle_name', models.CharField(blank=True, max_length=255, null=True)), ('last_name', models.CharField(max_length=255)), ('email', models.EmailField(max_length=255, unique=True)), - ('phone', models.CharField(max_length=15, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), + ('phone', models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), ('username', models.CharField(blank=True, max_length=255, null=True, unique=True)), ('password', models.CharField(max_length=255)), - ('active_status', models.BooleanField(default=True)), - ('position', models.CharField(choices=[('clerk', 'Clerk'), ('assistant', 'Assistant'), ('guard', 'Guard')], max_length=255)), + ('gender', models.CharField(choices=[('M', 'Male'), ('F', 'Female')], max_length=1)), + ('position', models.CharField(choices=[('Lab Assistant', 'Lab Assistant'), ('Librarian', 'Librarian'), ('Accountant', 'Accountant'), ('Guard', 'Guard'), ('Driver', 'Driver'), ('Helper', 'Helper'), ('Cleaner', 'Cleaner')], max_length=255)), ('address', models.CharField(max_length=255)), - ('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='auth.group')), + ('marital_status', models.CharField(choices=[('married', 'Married'), ('widowed', 'Widowed'), ('separated', 'Separated'), ('divorced', 'Divorced'), ('single', 'Single')], max_length=10)), + ('date_of_joining', models.DateField()), + ('group', models.ManyToManyField(blank=True, to='auth.group')), + ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - 'abstract': False, + 'verbose_name': 'Staff', + 'verbose_name_plural': 'Staff', }, ), migrations.CreateModel( name='Student', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active_status', models.BooleanField(default=True)), ('first_name', models.CharField(max_length=255)), + ('middle_name', models.CharField(blank=True, max_length=255, null=True)), ('last_name', models.CharField(max_length=255)), ('email', models.EmailField(max_length=255, unique=True)), - ('phone', models.CharField(max_length=15, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), + ('phone', models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), ('username', models.CharField(blank=True, max_length=255, null=True, unique=True)), ('password', models.CharField(max_length=255)), - ('active_status', models.BooleanField(default=True)), + ('address', models.CharField(max_length=255)), + ('date_of_birth', models.DateField()), ('gender', models.CharField(choices=[('M', 'Male'), ('F', 'Female')], max_length=1)), + ('father_name', models.CharField(max_length=100)), + ('father_phone_no', models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), + ('father_email', models.EmailField(blank=True, max_length=254, null=True)), + ('father_occupation', 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)), + ('mother_name', models.CharField(max_length=100)), + ('mother_phone_no', models.CharField(blank=True, max_length=10, null=True, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), + ('mother_email', models.EmailField(blank=True, max_length=254, null=True)), + ('mother_occupation', 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)), + ('guardian_name', models.CharField(max_length=100)), + ('guardian_phone_no', models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), + ('guardian_email', models.EmailField(blank=True, max_length=254, null=True)), + ('relationship_with_student', models.CharField(choices=[('Father', 'Father'), ('Mother', 'Mother'), ('Brother', 'Brother'), ('Sister', 'Sister'), ('Uncle', 'Uncle'), ('Aunt', 'Aunt'), ('Grandfather', 'Grandfather')], max_length=45)), ('academic_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.academicyear')), ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.course')), - ('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='auth.group')), + ('group', models.ManyToManyField(blank=True, to='auth.group')), + ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - 'abstract': False, + 'verbose_name': 'Student', + 'verbose_name_plural': 'Students', }, ), migrations.CreateModel( @@ -87,24 +144,69 @@ class Migration(migrations.Migration): ('semester_or_year_number', models.PositiveIntegerField()), ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='subjects', to='app.course')), ], + options={ + 'verbose_name': 'Subject', + 'verbose_name_plural': 'Subjects', + }, + ), + migrations.CreateModel( + name='Result', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('practical_marks', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5)), + ('exam_marks', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5)), + ('total_marks', models.DecimalField(decimal_places=2, default=Decimal('0.00'), editable=False, max_digits=5)), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.student')), + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.subject')), + ], + options={ + 'verbose_name': 'Result', + 'verbose_name_plural': 'Results', + }, + ), + migrations.CreateModel( + name='Attendance', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('date', models.DateField()), + ('status', models.CharField(choices=[('present', 'Present'), ('absent', 'Absent')], max_length=10)), + ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.student')), + ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.subject')), + ], + options={ + 'verbose_name': 'Attendance', + 'verbose_name_plural': 'Attendance Records', + }, ), migrations.CreateModel( name='Teacher', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active_status', models.BooleanField(default=True)), ('first_name', models.CharField(max_length=255)), + ('middle_name', models.CharField(blank=True, max_length=255, null=True)), ('last_name', models.CharField(max_length=255)), ('email', models.EmailField(max_length=255, unique=True)), - ('phone', models.CharField(max_length=15, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), + ('phone', models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')])), ('username', models.CharField(blank=True, max_length=255, null=True, unique=True)), ('password', models.CharField(max_length=255)), - ('active_status', models.BooleanField(default=True)), ('address', models.CharField(max_length=255)), ('gender', models.CharField(choices=[('M', 'Male'), ('F', 'Female')], max_length=1)), - ('group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='auth.group')), + ('qualification', models.CharField(choices=[('BA', 'BA'), ('BBS', 'BBS'), ('BE', 'BE'), ('BEd', 'BEd'), ('BSc', 'BSc'), ('MA', 'MA'), ('MSc', 'MSc'), ('MBS', 'MBS'), ('ME', 'ME'), ('PhD', 'PhD'), ('Others', 'Others')], max_length=45)), + ('specialization', models.CharField(max_length=255)), + ('marital_status', models.CharField(choices=[('married', 'Married'), ('widowed', 'Widowed'), ('separated', 'Separated'), ('divorced', 'Divorced'), ('single', 'Single')], max_length=10)), + ('date_of_joining', models.DateField()), + ('group', models.ManyToManyField(blank=True, to='auth.group')), + ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ - 'abstract': False, + 'verbose_name': 'Teacher', + 'verbose_name_plural': 'Teachers', }, ), + migrations.AddField( + model_name='subject', + name='teacher', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='subjects', to='app.teacher'), + ), ] diff --git a/app/migrations/0002_staff_user_student_user_teacher_user.py b/app/migrations/0002_staff_user_student_user_teacher_user.py deleted file mode 100644 index 99651af..0000000 --- a/app/migrations/0002_staff_user_student_user_teacher_user.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 04:39 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='staff', - name='user', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='student', - name='user', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='teacher', - name='user', - field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/migrations/0003_alter_staff_user_alter_student_user_and_more.py b/app/migrations/0003_alter_staff_user_alter_student_user_and_more.py deleted file mode 100644 index d50ada5..0000000 --- a/app/migrations/0003_alter_staff_user_alter_student_user_and_more.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 04:57 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0002_staff_user_student_user_teacher_user'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterField( - model_name='staff', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='student', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='teacher', - name='user', - field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/migrations/0004_remove_staff_user_remove_student_user_and_more.py b/app/migrations/0004_remove_staff_user_remove_student_user_and_more.py deleted file mode 100644 index 4fb106d..0000000 --- a/app/migrations/0004_remove_staff_user_remove_student_user_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 04:59 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0003_alter_staff_user_alter_student_user_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='staff', - name='user', - ), - migrations.RemoveField( - model_name='student', - name='user', - ), - migrations.RemoveField( - model_name='teacher', - name='user', - ), - ] diff --git a/app/migrations/0005_staff_user_student_user_teacher_user.py b/app/migrations/0005_staff_user_student_user_teacher_user.py deleted file mode 100644 index 663ab8b..0000000 --- a/app/migrations/0005_staff_user_student_user_teacher_user.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 05:01 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0004_remove_staff_user_remove_student_user_and_more'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='staff', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='student', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='teacher', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/migrations/0006_remove_staff_user_remove_student_user_and_more.py b/app/migrations/0006_remove_staff_user_remove_student_user_and_more.py deleted file mode 100644 index 8dbf2cd..0000000 --- a/app/migrations/0006_remove_staff_user_remove_student_user_and_more.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 05:10 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0005_staff_user_student_user_teacher_user'), - ] - - operations = [ - migrations.RemoveField( - model_name='staff', - name='user', - ), - migrations.RemoveField( - model_name='student', - name='user', - ), - migrations.RemoveField( - model_name='teacher', - name='user', - ), - ] diff --git a/app/migrations/0007_staff_user_student_user_teacher_user.py b/app/migrations/0007_staff_user_student_user_teacher_user.py deleted file mode 100644 index dac0dc5..0000000 --- a/app/migrations/0007_staff_user_student_user_teacher_user.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 05:11 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0006_remove_staff_user_remove_student_user_and_more'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AddField( - model_name='staff', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='student', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - migrations.AddField( - model_name='teacher', - name='user', - field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/app/migrations/0008_alter_staff_phone_alter_student_phone_and_more.py b/app/migrations/0008_alter_staff_phone_alter_student_phone_and_more.py deleted file mode 100644 index 0ce4a0a..0000000 --- a/app/migrations/0008_alter_staff_phone_alter_student_phone_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 06:43 - -import django.core.validators -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0007_staff_user_student_user_teacher_user'), - ] - - operations = [ - migrations.AlterField( - model_name='staff', - name='phone', - field=models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')]), - ), - migrations.AlterField( - model_name='student', - name='phone', - field=models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')]), - ), - migrations.AlterField( - model_name='teacher', - name='phone', - field=models.CharField(max_length=10, validators=[django.core.validators.RegexValidator('^\\+?1?\\d{9,15}$')]), - ), - ] diff --git a/app/migrations/0009_attendance.py b/app/migrations/0009_attendance.py deleted file mode 100644 index b175bed..0000000 --- a/app/migrations/0009_attendance.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 06:45 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0008_alter_staff_phone_alter_student_phone_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='Attendance', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('date', models.DateField()), - ('status', models.CharField(choices=[('present', 'Present'), ('absent', 'Absent')], max_length=10)), - ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.student')), - ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.subject')), - ], - ), - ] diff --git a/app/migrations/0010_result.py b/app/migrations/0010_result.py deleted file mode 100644 index f4f9c49..0000000 --- a/app/migrations/0010_result.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 09:41 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0009_attendance'), - ] - - operations = [ - migrations.CreateModel( - name='Result', - fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('practical_marks', models.IntegerField()), - ('exam_marks', models.IntegerField()), - ('total_marks', models.IntegerField(editable=False)), - ('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.student')), - ('subject', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='app.subject')), - ], - ), - ] diff --git a/app/migrations/0011_alter_result_total_marks.py b/app/migrations/0011_alter_result_total_marks.py deleted file mode 100644 index 4abd397..0000000 --- a/app/migrations/0011_alter_result_total_marks.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 09:46 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0010_result'), - ] - - operations = [ - migrations.AlterField( - model_name='result', - name='total_marks', - field=models.IntegerField(), - ), - ] diff --git a/app/migrations/0012_remove_result_total_marks.py b/app/migrations/0012_remove_result_total_marks.py deleted file mode 100644 index 47c1fa4..0000000 --- a/app/migrations/0012_remove_result_total_marks.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 09:54 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0011_alter_result_total_marks'), - ] - - operations = [ - migrations.RemoveField( - model_name='result', - name='total_marks', - ), - ] diff --git a/app/migrations/0013_result_total_marks.py b/app/migrations/0013_result_total_marks.py deleted file mode 100644 index 3750d5c..0000000 --- a/app/migrations/0013_result_total_marks.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 09:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0012_remove_result_total_marks'), - ] - - operations = [ - migrations.AddField( - model_name='result', - name='total_marks', - field=models.IntegerField(default=0), - ), - ] diff --git a/app/migrations/0014_alter_result_exam_marks_alter_result_practical_marks_and_more.py b/app/migrations/0014_alter_result_exam_marks_alter_result_practical_marks_and_more.py deleted file mode 100644 index cce7df6..0000000 --- a/app/migrations/0014_alter_result_exam_marks_alter_result_practical_marks_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 10:00 - -from decimal import Decimal -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0013_result_total_marks'), - ] - - operations = [ - migrations.AlterField( - model_name='result', - name='exam_marks', - field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5), - ), - migrations.AlterField( - model_name='result', - name='practical_marks', - field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=5), - ), - migrations.AlterField( - model_name='result', - name='total_marks', - field=models.DecimalField(decimal_places=2, default=Decimal('0.00'), editable=False, max_digits=5), - ), - ] diff --git a/app/migrations/0015_alter_staff_group_alter_student_group_and_more.py b/app/migrations/0015_alter_staff_group_alter_student_group_and_more.py deleted file mode 100644 index 1a45b71..0000000 --- a/app/migrations/0015_alter_staff_group_alter_student_group_and_more.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 10:14 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0014_alter_result_exam_marks_alter_result_practical_marks_and_more'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.AlterField( - model_name='staff', - name='group', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.group'), - ), - migrations.AlterField( - model_name='student', - name='group', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.group'), - ), - migrations.AlterField( - model_name='teacher', - name='group', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.group'), - ), - ] diff --git a/app/migrations/0016_remove_staff_group_remove_student_group_and_more.py b/app/migrations/0016_remove_staff_group_remove_student_group_and_more.py deleted file mode 100644 index ca0b799..0000000 --- a/app/migrations/0016_remove_staff_group_remove_student_group_and_more.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 10:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0015_alter_staff_group_alter_student_group_and_more'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.RemoveField( - model_name='staff', - name='group', - ), - migrations.RemoveField( - model_name='student', - name='group', - ), - migrations.RemoveField( - model_name='teacher', - name='group', - ), - migrations.AddField( - model_name='staff', - name='group', - field=models.ManyToManyField(blank=True, null=True, to='auth.group'), - ), - migrations.AddField( - model_name='student', - name='group', - field=models.ManyToManyField(blank=True, null=True, to='auth.group'), - ), - migrations.AddField( - model_name='teacher', - name='group', - field=models.ManyToManyField(blank=True, null=True, to='auth.group'), - ), - ] diff --git a/app/migrations/0017_alter_staff_group_alter_student_group_and_more.py b/app/migrations/0017_alter_staff_group_alter_student_group_and_more.py deleted file mode 100644 index c2f3e95..0000000 --- a/app/migrations/0017_alter_staff_group_alter_student_group_and_more.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 5.1.4 on 2025-01-07 10:19 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('app', '0016_remove_staff_group_remove_student_group_and_more'), - ('auth', '0012_alter_user_first_name_max_length'), - ] - - operations = [ - migrations.AlterField( - model_name='staff', - name='group', - field=models.ManyToManyField(blank=True, to='auth.group'), - ), - migrations.AlterField( - model_name='student', - name='group', - field=models.ManyToManyField(blank=True, to='auth.group'), - ), - migrations.AlterField( - model_name='teacher', - name='group', - field=models.ManyToManyField(blank=True, to='auth.group'), - ), - ] diff --git a/app/models.py b/app/models.py index 9f2ef88..8fa8550 100644 --- a/app/models.py +++ b/app/models.py @@ -4,11 +4,14 @@ from django.core.validators import RegexValidator from django.contrib.auth.hashers import make_password import random from decimal import Decimal +from datetime import time, timedelta, datetime # Abstract BaseUser Model class BaseUser(models.Model): + active_status = models.BooleanField(default=True) first_name = models.CharField(max_length=255) + middle_name = models.CharField(max_length=255, blank=True, null=True) last_name = models.CharField(max_length=255) email = models.EmailField(max_length=255, unique=True) phone = models.CharField( @@ -16,7 +19,7 @@ class BaseUser(models.Model): ) username = models.CharField(max_length=255, unique=True, blank=True, null=True) password = models.CharField(max_length=255) - active_status = models.BooleanField(default=True) + address = models.CharField(max_length=255) group = models.ManyToManyField(Group, blank=True) user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True) @@ -32,13 +35,14 @@ class BaseUser(models.Model): if self._state.adding or not self.password.startswith("pbkdf2_"): self.password = make_password(self.password) - # Create or update the linked User object + # Sync with Django User model if not self.user: self.user = User.objects.create( username=self.username, 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, @@ -48,17 +52,13 @@ 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 - # Save the BaseUser instance super().save(*args, **kwargs) - - # Update the user's groups self.user.groups.set(self.group.all()) - - # Save the user object self.user.save() def __str__(self): @@ -69,27 +69,157 @@ class BaseUser(models.Model): class Teacher(BaseUser): address = models.CharField(max_length=255) gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")]) + qualification_choice = ( + ("BA", "BA"), + ("BBS", "BBS"), + ("BE", "BE"), + ("BEd", "BEd"), + ("BSc", "BSc"), + ("MA", "MA"), + ("MSc", "MSc"), + ("MBS", "MBS"), + ("ME", "ME"), + ("PhD", "PhD"), + ("Others", "Others"), + ) + qualification = models.CharField(choices=qualification_choice, max_length=45) + specialization = models.CharField(max_length=255) + marital_status_choice = ( + ("married", "Married"), + ("widowed", "Widowed"), + ("separated", "Separated"), + ("divorced", "Divorced"), + ("single", "Single"), + ) + marital_status = models.CharField(choices=marital_status_choice, max_length=10) + date_of_joining = models.DateField() + + class Meta: + verbose_name = "Teacher" + verbose_name_plural = "Teachers" # Student model class Student(BaseUser): + date_of_birth = models.DateField(blank=False) gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")]) course = models.ForeignKey("Course", on_delete=models.CASCADE) academic_year = models.ForeignKey("AcademicYear", on_delete=models.CASCADE) + father_name = models.CharField(max_length=100) + father_phone_no = models.CharField( + max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")] + ) + 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"), + ("N/A", "N/A"), + ) + father_occupation = models.CharField( + choices=father_occupation_choice, max_length=45, default="N/A" + ) + mother_name = models.CharField(max_length=100) + mother_phone_no = models.CharField( + max_length=10, + validators=[RegexValidator(r"^\+?1?\d{9,15}$")], + blank=True, + null=True, + ) + 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"), + ("N/A", "N/A"), + ) + mother_occupation = models.CharField( + choices=mother_occupation_choice, max_length=45, default="N/A" + ) + guardian_name = models.CharField(max_length=100) + guardian_phone_no = models.CharField( + max_length=10, validators=[RegexValidator(r"^\+?1?\d{9,15}$")] + ) + guardian_email = models.EmailField(blank=True, null=True) + relationship_choice = ( + ("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 + ) + + class Meta: + verbose_name = "Student" + verbose_name_plural = "Students" # Staff model class Staff(BaseUser): + gender = models.CharField(max_length=1, choices=[("M", "Male"), ("F", "Female")]) position = models.CharField( max_length=255, - choices=[("clerk", "Clerk"), ("assistant", "Assistant"), ("guard", "Guard")], + choices=[ + ("Lab Assistant", "Lab Assistant"), + ("Librarian", "Librarian"), + ("Accountant", "Accountant"), + ("Guard", "Guard"), + ("Driver", "Driver"), + ("Helper", "Helper"), + ("Cleaner", "Cleaner"), + ], ) address = models.CharField(max_length=255) + marital_status_choice = ( + ("married", "Married"), + ("widowed", "Widowed"), + ("separated", "Separated"), + ("divorced", "Divorced"), + ("single", "Single"), + ) + marital_status = models.CharField(choices=marital_status_choice, max_length=10) + date_of_joining = models.DateField() + + class Meta: + verbose_name = "Staff" + verbose_name_plural = "Staff" # Course model 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"), + ) + level = models.CharField(choices=level_choice, max_length=45) course_type = models.CharField( max_length=10, choices=[("semester", "Semester-based"), ("yearly", "Yearly-based")], @@ -99,14 +229,22 @@ class Course(models.Model): def __str__(self): return self.name + class Meta: + verbose_name = "Course" + verbose_name_plural = "Courses" + # AcademicYear model class AcademicYear(models.Model): - batch = models.CharField(max_length=255, unique=True) + year = models.CharField(max_length=255, unique=True) def __str__(self): return self.batch + class Meta: + verbose_name = "Academic Year" + verbose_name_plural = "Academic Years" + # Subject model class Subject(models.Model): @@ -115,10 +253,21 @@ class Subject(models.Model): "Course", on_delete=models.CASCADE, related_name="subjects" ) semester_or_year_number = models.PositiveIntegerField() + teacher = models.ForeignKey( + "Teacher", + on_delete=models.CASCADE, + related_name="subjects", + null=True, + blank=True, + ) def __str__(self): return f"{self.name} (Sem/Year {self.semester_or_year_number})" + class Meta: + verbose_name = "Subject" + verbose_name_plural = "Subjects" + # NewsEvent model class NewsEvent(models.Model): @@ -132,6 +281,10 @@ class NewsEvent(models.Model): def __str__(self): return f"{self.title} ({self.get_type_display()})" + class Meta: + verbose_name = "News/Event" + verbose_name_plural = "News/Events" + # Attendance model class Attendance(models.Model): @@ -143,6 +296,10 @@ class Attendance(models.Model): max_length=10, choices=[("present", "Present"), ("absent", "Absent")] ) + class Meta: + verbose_name = "Attendance" + verbose_name_plural = "Attendance Records" + # Result model class Result(models.Model): @@ -159,10 +316,6 @@ class Result(models.Model): max_digits=5, decimal_places=2, default=Decimal("0.00"), editable=False ) - def clean(self): - self.total_marks = self.get_total() - super().clean() - def get_total(self): return Decimal(str(self.practical_marks or 0)) + Decimal( str(self.exam_marks or 0) @@ -174,3 +327,25 @@ class Result(models.Model): def __str__(self): return f"{self.student} - {self.subject} - Total: {self.total_marks}" + + class Meta: + verbose_name = "Result" + verbose_name_plural = "Results" + + +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_year = models.PositiveIntegerField() + quantity = models.PositiveIntegerField() + available = models.CharField(max_length=1, choices=[("Y", "Yes"), ("N", "NO")]) + date_added = models.DateField(auto_now_add=True) + + def __str__(self): + return self.book_name + + class Meta: + verbose_name = "Library" + verbose_name_plural = "Library" diff --git a/app/views.py b/app/views.py index 563c21f..50ad71f 100644 --- a/app/views.py +++ b/app/views.py @@ -2,8 +2,11 @@ from django.shortcuts import render, get_object_or_404 from django.contrib.admin import site from app.models import Student, NewsEvent, Subject +from django.contrib.auth.decorators import login_required + # Dashboard for admin +@login_required(login_url="login") def dashboard(request): context = { **site.each_context(request), diff --git a/student_management_system/jazzmin.py b/student_management_system/jazzmin.py new file mode 100644 index 0000000..44e8f4a --- /dev/null +++ b/student_management_system/jazzmin.py @@ -0,0 +1,120 @@ + +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, + + ############ + # 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"]}, + + # 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}, + # {"model": "auth.user"} + # ], + # + ############# + # 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"], + + + + ################# + # Related Modal # + ################# + # Use modals instead of popups + "related_modal_active": False, + + ############# + # UI Tweaks # + ############# + # Relative paths to custom CSS/JS scripts (must be present in static files) + "custom_css": None, + "custom_js": None, + # Whether to show the UI customizer on the sidebar + "show_ui_builder": False, + + ############### + # Change view # + ############### + # Render out the change view as a single form, or in tabs, current options are + # - single + # - horizontal_tabs (default) + # - vertical_tabs + # - collapsible + # - carousel + "changeform_format": "horizontal_tabs", + # override change forms on a per modeladmin basis + "changeform_format_overrides": {"auth.user": "collapsible", "auth.group": "vertical_tabs"}, +} + +JAZZMIN_UI_TWEAKS = { + "theme": "lumen", + # "dark_mode_theme": "cyborg", +} diff --git a/student_management_system/settings.py b/student_management_system/settings.py index b4b4249..5d73876 100644 --- a/student_management_system/settings.py +++ b/student_management_system/settings.py @@ -11,23 +11,23 @@ https://docs.djangoproject.com/en/5.1/ref/settings/ """ import os +from student_management_system.jazzmin import JAZZMIN_SETTINGS, JAZZMIN_UI_TWEAKS + -# Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-@+&tj2vh)c2b+aiei14s%euz*@wt)#oso1ofowr!i4r93$ose6" +SECRET_KEY = "django-insecure-4^0p#+b(zk1vavtg#+f)i$hog(!v(v4=bsf*d#-v=kic)3%kbx" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] - +ALLOWED_HOSTS = ["*", "192.168.18.225"] +JAZZMIN_SETTINGS = JAZZMIN_SETTINGS +JAZZMIN_UI_TWEAKS = JAZZMIN_UI_TWEAKS # Application definition INSTALLED_APPS = [ @@ -101,7 +101,6 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] - # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ @@ -117,18 +116,16 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ +STATIC_URL = "static/" +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), +] +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") -STATIC_URL = "/static/" -# STATICFILES_DIRS = [ -# os.path.join(BASE_DIR, "static"), -# ] -# STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") -# -# STATICFILES_FINDERS = [ -# "django.contrib.staticfiles.finders.FileSystemFinder", -# "django.contrib.staticfiles.finders.AppDirectoriesFinder", -# ] - +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] MEDIA_URL = "/media/" MEDIA_ROOT = os.path.join(BASE_DIR, "media") diff --git a/student_management_system/urls.py b/student_management_system/urls.py index b97f55e..439e79c 100644 --- a/student_management_system/urls.py +++ b/student_management_system/urls.py @@ -14,9 +14,37 @@ Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ -from django.contrib import admin -from django.urls import path -urlpatterns = [ - path('admin/', admin.site.urls), -] +from django.contrib import admin +from django.urls import path, include +from django.views.generic import RedirectView +from django.conf import settings +from django.conf.urls.static import static + +urlpatterns = ( + [ + path("admin/", admin.site.urls), + path("", RedirectView.as_view(url="/admin/", permanent=False)), + ] + + 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) +# ) +