├── apps ├── attendance │ ├── __init__.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ ├── pagination.py │ ├── permissions.py │ ├── models.py │ ├── serializers.py │ └── views.py ├── dailystat │ ├── __init__.py │ ├── apps.py │ ├── admin.py │ ├── urls.py │ ├── serializers.py │ ├── tasks.py │ ├── filters.py │ ├── models.py │ └── views.py └── authentication │ ├── __init__.py │ ├── apps.py │ ├── urls.py │ ├── validation.py │ ├── backends.py │ ├── models.py │ ├── managers.py │ ├── serializers.py │ ├── admin.py │ ├── custom_jwt.py │ └── views.py ├── dump.rdb ├── config ├── __init__.py ├── middleware.py ├── asgi.py ├── wsgi.py ├── celery.py ├── urls.py └── settings.py ├── .gitignore ├── .env.example ├── requirements.txt ├── .github └── FUNDING.yml ├── manage.py ├── LICENSE └── README.md /apps/attendance/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/dailystat/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/authentication/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sevbo2003/student-attendance-system/HEAD/dump.rdb -------------------------------------------------------------------------------- /config/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import app as celery_app 2 | 3 | __all__ = ('celery_app',) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .env 3 | __pycache__ 4 | *.sqlite3 5 | .vscode/ 6 | .idea/ 7 | migrations/ 8 | 9 | celerybeat-schedule.db 10 | ajou.json -------------------------------------------------------------------------------- /apps/dailystat/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DailystatConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'apps.dailystat' 7 | -------------------------------------------------------------------------------- /apps/attendance/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AttendanceConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'apps.attendance' 7 | -------------------------------------------------------------------------------- /apps/authentication/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AuthenticationConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'apps.authentication' 7 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | SECRET_KEY=fsfun98928r729(@#72378nn92839*27n9*32) 2 | DEBUG=True 3 | ALLOWED_HOSTS=* localhost 4 | 5 | DB_NAME= 6 | DB_USER= 7 | DB_PASSWORD= 8 | DB_HOST= 9 | DB_PORT= 10 | 11 | REDIS_URL= 12 | CORS_ORIGIN_WHITELIST= -------------------------------------------------------------------------------- /apps/dailystat/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from apps.dailystat.models import DailyAttendanceStat 3 | 4 | 5 | @admin.register(DailyAttendanceStat) 6 | class DailyAttendanceStatAdmin(admin.ModelAdmin): 7 | list_display = ('student', 'day') 8 | list_filter = ('day',) 9 | search_fields = ('student__first_name', 'student__last_name', 'day') -------------------------------------------------------------------------------- /apps/dailystat/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework.routers import DefaultRouter 3 | from apps.dailystat.views import DailyAttendanceStatViewSet 4 | 5 | router = DefaultRouter() 6 | router.register('', DailyAttendanceStatViewSet, basename='dailystat') 7 | 8 | urlpatterns = [ 9 | path('', include(router.urls)), 10 | ] -------------------------------------------------------------------------------- /config/middleware.py: -------------------------------------------------------------------------------- 1 | # Custom middleware to test working of middleware 2 | import json 3 | class TestMiddleware: 4 | def __init__(self, get_response): 5 | self.get_response = get_response 6 | 7 | def __call__(self, request): 8 | print('Middleware called') 9 | response = self.get_response(request) 10 | return response -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==3.2.15 2 | djangorestframework==3.13.1 3 | djangorestframework-simplejwt==5.2.0 4 | python-dotenv==0.21.0 5 | django-filter==22.1 6 | psycopg2-binary==2.9.3 7 | django-cors-headers==3.13.0 8 | celery==5.2.7 9 | django-celery-beat==2.4.0 10 | django_rest_swagger==2.2.0 11 | django-redis==5.2.0 12 | django-debug-toolbar==3.7.0 13 | redis==4.3.4 -------------------------------------------------------------------------------- /apps/authentication/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from apps.authentication.views import UserViewSet, TeacherViewSet 3 | from rest_framework.routers import DefaultRouter 4 | 5 | router = DefaultRouter() 6 | router.register('users', UserViewSet, basename='users') 7 | router.register('teachers', TeacherViewSet, basename='teachers') 8 | 9 | 10 | urlpatterns = [ 11 | path('', include(router.urls)), 12 | ] -------------------------------------------------------------------------------- /config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /apps/authentication/validation.py: -------------------------------------------------------------------------------- 1 | import re 2 | from rest_framework.validators import ValidationError 3 | 4 | regex = re.compile(r'([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+') 5 | 6 | def isValid(email): 7 | try: 8 | if re.fullmatch(regex, email): 9 | return True 10 | else: 11 | raise ValidationError({"detail":"Invalid email address"}) 12 | except: 13 | raise ValidationError({"detail":"Invalid email address"}) -------------------------------------------------------------------------------- /apps/authentication/backends.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.contrib.auth.backends import ModelBackend 3 | 4 | 5 | class EmailBackend(ModelBackend): 6 | def authenticate(self, request, **kwargs): 7 | UserModel = get_user_model() 8 | try: 9 | email = kwargs.get('email', None) 10 | if email is None: 11 | email = kwargs.get('username', None) 12 | user = UserModel.objects.get(email=email) 13 | if user.check_password(kwargs.get('password', None)): 14 | return user 15 | except UserModel.DoesNotExist: 16 | return None 17 | return None -------------------------------------------------------------------------------- /apps/attendance/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from apps.attendance.models import * 3 | 4 | 5 | admin.site.register(Attendance) 6 | admin.site.register(AttendanceReport) 7 | admin.site.register(Group) 8 | admin.site.register(Student) 9 | 10 | 11 | class M2MInline(admin.TabularInline): 12 | model = Subject.group.through 13 | extra = 1 14 | 15 | 16 | class SubjectAdmin(admin.ModelAdmin): 17 | inlines = [M2MInline] 18 | list_display = ['name', 'slug', 'teacher'] 19 | list_filter = ['teacher'] 20 | search_fields = ['name', 'slug', 'teacher__first_name', 'teacher__last_name'] 21 | prepopulated_fields = {'slug': ('name',)} 22 | 23 | admin.site.register(Subject, SubjectAdmin) -------------------------------------------------------------------------------- /config/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from celery import Celery 4 | from celery.schedules import crontab 5 | 6 | 7 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 8 | 9 | app = Celery('config') 10 | app.config_from_object('django.conf:settings', namespace='CELERY') 11 | # Configure redis as the broker 12 | app.conf.broker_url = 'redis://localhost:6379/0' 13 | app.conf.result_backend = 'redis://localhost:6379/0' 14 | 15 | app.autodiscover_tasks() 16 | 17 | # Configure celery beat 18 | app.conf.beat_schedule = { 19 | 'send-daily-stats': { 20 | 'task': 'apps.dailystat.tasks.send_daily_stats', 21 | 'schedule': crontab(hour=0, minute=22, day_of_week='*'), 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /apps/attendance/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from rest_framework.routers import DefaultRouter 3 | from apps.attendance.views import StudentViewSet, GroupViewSet, SubjectViewSet, AttendanceViewSet, AttendanceReportViewSet 4 | 5 | 6 | router = DefaultRouter() 7 | 8 | router.register('students', StudentViewSet, basename='students') 9 | router.register('groups', GroupViewSet, basename='groups') 10 | router.register('subjects', SubjectViewSet, basename='subjects') 11 | router.register('attendances', AttendanceViewSet, basename='attendances') 12 | router.register('attendance-report', AttendanceReportViewSet, basename='attendance-reports') 13 | 14 | 15 | urlpatterns = [ 16 | path('', include(router.urls)), 17 | ] -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [sevbo2003] 4 | patreon: abdusamad 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: abdusamad 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /apps/authentication/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractUser 3 | from apps.authentication.managers import CustomUserManager 4 | 5 | class UserType(models.TextChoices): 6 | ADMIN = 'admin', 'Admin' 7 | TEACHER = 'teacher', 'Teacher' 8 | 9 | 10 | class User(AbstractUser): 11 | email = models.EmailField(unique=True) 12 | username=None 13 | USERNAME_FIELD = 'email' 14 | REQUIRED_FIELDS = ['first_name', 'last_name'] 15 | user_type = models.CharField(choices=UserType.choices, max_length=10, default=UserType.TEACHER) 16 | objects = CustomUserManager() 17 | 18 | def __str__(self): 19 | return self.email 20 | 21 | class Meta: 22 | verbose_name = 'User' 23 | verbose_name_plural = 'Users' 24 | -------------------------------------------------------------------------------- /apps/dailystat/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from apps.dailystat.models import DailyAttendanceStat 3 | 4 | 5 | class DailyAttendanceStatSerializer(serializers.ModelSerializer): 6 | student = serializers.SerializerMethodField() 7 | subjects = serializers.SerializerMethodField() 8 | 9 | class Meta: 10 | model = DailyAttendanceStat 11 | fields = '__all__' 12 | 13 | def get_student(self, obj): 14 | return { 15 | "id": obj.student.id, 16 | "first_name": obj.student.first_name, 17 | "last_name": obj.student.last_name, 18 | } 19 | 20 | def get_subjects(self, obj): 21 | return [ 22 | { 23 | "id": i.id, 24 | "name": i.name.split('(')[0].rstrip(), 25 | } 26 | for i in obj.subjects.all() 27 | ] -------------------------------------------------------------------------------- /apps/attendance/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import PageNumberPagination 2 | from rest_framework.response import Response 3 | 4 | 5 | DEFAULT_PAGE = 1 6 | DEFAULT_PAGE_SIZE = 10 7 | 8 | class CustomPagination(PageNumberPagination): 9 | page = DEFAULT_PAGE 10 | page_size = DEFAULT_PAGE_SIZE 11 | page_size_query_param = 'page_size' 12 | 13 | 14 | def get_paginated_response(self, data): 15 | return Response({ 16 | 'links': { 17 | 'next': self.get_next_link(), 18 | 'previous': self.get_previous_link() 19 | }, 20 | 'total': self.page.paginator.count, 21 | 'page': int(self.request.GET.get('page', DEFAULT_PAGE)), # can not set default = self.page 22 | 'page_size': int(self.request.GET.get('page_size', self.page_size)), 23 | 'results': data 24 | }) 25 | -------------------------------------------------------------------------------- /apps/dailystat/tasks.py: -------------------------------------------------------------------------------- 1 | from config.celery import app 2 | from apps.dailystat.models import DailyAttendanceStat 3 | from apps.attendance.models import Subject, Student 4 | from apps.attendance.models import AttendanceReport, Satus, Subject 5 | from datetime import datetime 6 | 7 | 8 | @app.task 9 | def send_daily_stats(): 10 | query = AttendanceReport.objects.filter(attendance__date = datetime.today().date(), status=Satus.ABSENT) 11 | for i in query: 12 | if DailyAttendanceStat.objects.filter(day=i.attendance.date, student=i.student).exists(): 13 | a = DailyAttendanceStat.objects.get(student=i.student, day=i.attendance.date) 14 | a.subjects.add(i.attendance.subject) 15 | a.save() 16 | else: 17 | x = DailyAttendanceStat.objects.create(student = i.student, day = i.attendance.date) 18 | x.subjects.add(i.attendance.subject) 19 | x.save() -------------------------------------------------------------------------------- /apps/attendance/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | from apps.attendance.models import Attendance, Subject, Group 3 | from apps.authentication.models import User, UserType 4 | 5 | 6 | class IsTeacher(BasePermission): 7 | def has_permission(self, request, view): 8 | if request.user.is_authenticated: 9 | return True 10 | return False 11 | 12 | def has_object_permission(self, request, view, obj): 13 | if request.user.is_authenticated: 14 | if request.user.user_type in [UserType.TEACHER, UserType.ADMIN]: 15 | if request.user.user_type == UserType.TEACHER: 16 | if request.user == obj: 17 | return True 18 | else: 19 | return False 20 | else: 21 | return True 22 | return False 23 | return False -------------------------------------------------------------------------------- /apps/dailystat/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | from apps.dailystat.models import DailyAttendanceStat 3 | from django.db.models import Q 4 | 5 | 6 | class DailyAttendanceStatFilter(filters.FilterSet): 7 | student = filters.CharFilter(method='filter_student') 8 | subjects = filters.CharFilter(method='filter_subjects_by_slug') 9 | group = filters.CharFilter(method='filter_group') 10 | 11 | class Meta: 12 | model = DailyAttendanceStat 13 | fields = ['student', 'subjects', 'group'] 14 | 15 | def filter_student(self, queryset, name, value): 16 | return queryset.filter(Q(student__first_name__icontains=value) | Q(student__last_name__icontains=value)) 17 | 18 | def filter_subjects_by_slug(self, queryset, name, value): 19 | return queryset.filter(subjects__slug=value) 20 | 21 | def filter_group(self, queryset, name, value): 22 | return queryset.filter(student__group__name=value) -------------------------------------------------------------------------------- /apps/authentication/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import BaseUserManager 2 | 3 | 4 | class CustomUserManager(BaseUserManager): 5 | def create_user(self, email, password, **extra_fields): 6 | if not email: 7 | raise ValueError("The email must be set") 8 | email = self.normalize_email(email) 9 | user = self.model(email=email, **extra_fields) 10 | user.save() 11 | user.set_password(password) 12 | user.save(using=self._db) 13 | return user 14 | 15 | def create_superuser(self, email, password, **extra_fields): 16 | extra_fields.setdefault('is_staff', True) 17 | extra_fields.setdefault('is_superuser', True) 18 | extra_fields.setdefault('is_active', True) 19 | 20 | if extra_fields.get('is_staff') is not True: 21 | raise ValueError('Superuser must have is_staff=True.') 22 | if extra_fields.get('is_superuser') is not True: 23 | raise ValueError('Superuser must have is_superuser=True.') 24 | return self.create_user(email, password, **extra_fields) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Abdusamad Malikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.conf import settings 4 | from rest_framework import urls as rest_auth 5 | from django.conf.urls.static import static 6 | from rest_framework_simplejwt.views import TokenRefreshView, TokenVerifyView 7 | from apps.authentication.custom_jwt import MyTokenObtainPairView 8 | from rest_framework_swagger.views import get_swagger_view 9 | 10 | schema_view = get_swagger_view(title='Student Attendance System API') 11 | 12 | 13 | urlpatterns = [ 14 | path('admin/', admin.site.urls), 15 | path('', schema_view), 16 | path('accounts/', include('apps.authentication.urls')), 17 | path('attendance/', include('apps.attendance.urls')), 18 | path('dailystat/', include('apps.dailystat.urls')), 19 | path('rest-auth/', include(rest_auth)), 20 | path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'), 21 | path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), 22 | path('api/token/verify/', TokenVerifyView.as_view(), name='token_verify'), 23 | ] 24 | 25 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -------------------------------------------------------------------------------- /apps/authentication/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from apps.authentication.models import User 3 | from rest_framework.exceptions import ValidationError 4 | 5 | 6 | class UserSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = User 9 | fields = ['id', 'email', 'first_name', 'last_name', 'user_type'] 10 | 11 | 12 | class RegisterSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = User 15 | fields = ['id', 'email', 'first_name', 'last_name', 'user_type', 'password'] 16 | extra_kwargs = {'password': {'write_only': True}} 17 | 18 | def create(self, validated_data): 19 | user_type = validated_data['user_type'] 20 | if user_type == 'admin': 21 | raise ValidationError({'user_type': 'Admin user cannot be created.'}) 22 | user = User.objects.create_user( 23 | email=validated_data['email'], 24 | first_name=validated_data['first_name'], 25 | last_name=validated_data['last_name'], 26 | user_type=validated_data['user_type'], 27 | password=validated_data['password'] 28 | ) 29 | return user -------------------------------------------------------------------------------- /apps/dailystat/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from apps.attendance.models import Subject, Student 3 | from apps.attendance.models import AttendanceReport, Satus, Subject 4 | 5 | 6 | class DailyAttendanceStat(models.Model): 7 | student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='daily_stats') 8 | subjects = models.ManyToManyField(Subject) 9 | day = models.DateField() 10 | 11 | class Meta: 12 | verbose_name = 'Daily Attendance Stat' 13 | verbose_name_plural = 'Daily Attendance Stats' 14 | ordering = ['-day'] 15 | 16 | def run_report_and_save(self): 17 | query = AttendanceReport.objects.filter(attendance__date__day=18, attendance__date__month=11, attendance__date__year=2022, status=Satus.ABSENT) 18 | for i in query: 19 | if self.objects.filter(day=i.attendance.date, student=i.student).exists(): 20 | a = self.objects.get(student=i.student, day=i.attendance.date) 21 | a.subjects.add(i.attendance.subject) 22 | a.save() 23 | else: 24 | x = self.objects.create(student = i.student, day = i.attendance.date) 25 | x.subjects.add(i.attendance.subject) 26 | x.save() -------------------------------------------------------------------------------- /apps/authentication/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from apps.authentication.models import User 3 | from django.contrib.auth.admin import UserAdmin 4 | from django.utils.translation import gettext, gettext_lazy as _ 5 | 6 | 7 | class CustomUserAdmin(UserAdmin): 8 | fieldsets = ( 9 | (None, {'fields': ('email', 'password')}), 10 | (_('Personal info'), {'fields': ('first_name', 'last_name', 'user_type')}), 11 | (_('Permissions'), { 12 | 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'), 13 | }), 14 | (_('Important dates'), {'fields': ('last_login', 'date_joined')}), 15 | ) 16 | add_fieldsets = ( 17 | (None, { 18 | 'classes': ('wide',), 19 | 'fields': ('email', 'first_name', 'last_name','user_type', 'password1', 'password2'), 20 | }), 21 | ) 22 | list_display = ('email', 'first_name', 'last_name', 'user_type', 'is_staff') 23 | list_filter = ('is_staff', 'user_type', 'is_superuser', 'is_active', 'groups') 24 | search_fields = ('email', 'first_name', 'last_name') 25 | ordering = ('email',) 26 | filter_horizontal = ('groups', 'user_permissions') 27 | list_per_page = 25 28 | 29 | 30 | admin.site.register(User, CustomUserAdmin) -------------------------------------------------------------------------------- /apps/dailystat/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | from apps.dailystat.models import DailyAttendanceStat 3 | from apps.dailystat.serializers import DailyAttendanceStatSerializer 4 | from apps.dailystat.filters import DailyAttendanceStatFilter 5 | from django_filters.rest_framework import DjangoFilterBackend 6 | from datetime import datetime 7 | from django.utils.decorators import method_decorator 8 | from django.views.decorators.cache import cache_page 9 | 10 | 11 | class DailyAttendanceStatViewSet(viewsets.ModelViewSet): 12 | queryset = DailyAttendanceStat.objects.filter(day=datetime.today().date()).order_by('student__last_name') 13 | serializer_class = DailyAttendanceStatSerializer 14 | http_method_names = ['get', 'head', 'options'] 15 | filterset_class = DailyAttendanceStatFilter 16 | filter_backends = [DjangoFilterBackend] 17 | 18 | # @method_decorator(cache_page(timeout=60*60*0.3)) 19 | # def dispatch(self, *args, **kwargs): 20 | # return super().dispatch(*args, **kwargs) 21 | 22 | def get_queryset(self): 23 | try: 24 | queryset = DailyAttendanceStat.objects.filter(day=datetime.today().date()).order_by('student__last_name') 25 | except: 26 | last_day = DailyAttendanceStat.objects.first().day 27 | queryset = DailyAttendanceStat.objects.filter(day=last_day).order_by('student__last_name') 28 | return queryset -------------------------------------------------------------------------------- /apps/authentication/custom_jwt.py: -------------------------------------------------------------------------------- 1 | # Update jwt serializer 2 | from rest_framework_simplejwt.serializers import TokenObtainPairSerializer 3 | from rest_framework.validators import ValidationError 4 | from apps.authentication.validation import isValid 5 | from apps.authentication.models import User 6 | 7 | 8 | class MyTokenObtainPairSerializer(TokenObtainPairSerializer): 9 | def validate(self, attrs): 10 | email = attrs.get('email') 11 | password = attrs.get('password') 12 | user = User.objects.filter(email=email).first() 13 | if isValid(email=email): 14 | if user: 15 | if user.check_password(password): 16 | data = super().validate(attrs) 17 | refresh = self.get_token(user) 18 | data['access'] = str(refresh.access_token) 19 | data['refresh'] = str(refresh) 20 | user_info = { 21 | 'id': user.id, 22 | 'first_name': user.first_name, 23 | 'last_name': user.last_name, 24 | 'user_type': user.user_type, 25 | } 26 | data['user'] = user_info 27 | return data 28 | else: 29 | raise ValidationError({'detail': 'Email or password is incorrect'}) 30 | else: 31 | raise ValidationError({'detail': 'Email or password is incorrect'}) 32 | else: 33 | raise ValidationError({'detail': 'Please enter a valid email address'}) 34 | 35 | def get_token(self, user): 36 | token = super().get_token(user) 37 | token['email'] = user.email 38 | return token 39 | 40 | 41 | # Update TokenObtainPairView 42 | from rest_framework_simplejwt.views import TokenObtainPairView 43 | 44 | 45 | class MyTokenObtainPairView(TokenObtainPairView): 46 | serializer_class = MyTokenObtainPairSerializer -------------------------------------------------------------------------------- /apps/authentication/views.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from rest_framework.viewsets import ModelViewSet 3 | from apps.authentication.models import User,UserType 4 | from apps.attendance.permissions import IsTeacher 5 | from apps.authentication.serializers import UserSerializer 6 | from rest_framework.permissions import IsAdminUser, IsAuthenticated 7 | from rest_framework.response import Response 8 | from rest_framework import status 9 | from apps.attendance.serializers import GroupSerializer, SubjectSerializer 10 | from rest_framework.decorators import action 11 | 12 | 13 | class UserViewSet(ModelViewSet): 14 | queryset = User.objects.all() 15 | serializer_class = UserSerializer 16 | permission_classes = [IsAdminUser] 17 | http_method_names: List[str] = ['get', 'head', 'options'] 18 | 19 | @action(detail=True, methods=['get']) 20 | def subjects(self, request, pk=None): 21 | user = self.get_object() 22 | subjects = user.subjects.all() 23 | serializer = SubjectSerializer(subjects, many=True) 24 | return Response(serializer.data) 25 | 26 | def get_permissions(self): 27 | if self.action == 'subjects': 28 | self.permission_classes = [IsAuthenticated, IsTeacher] 29 | return super().get_permissions() 30 | 31 | 32 | class TeacherViewSet(ModelViewSet): 33 | queryset = User.objects.filter(user_type=UserType.TEACHER) 34 | serializer_class = UserSerializer 35 | permission_classes = [IsAdminUser] 36 | http_method_names: List[str] = ['get','post', 'head', 'options'] 37 | 38 | def create(self, request, *args, **kwargs): 39 | if self.action == 'update_password': 40 | return super().create(request, *args, **kwargs) 41 | return Response({"detail": "You do not have permission to do this operations"}) 42 | 43 | @action(detail=True, methods=['get']) 44 | def subjects(self, request, pk=None): 45 | user = self.get_object() 46 | subjects = user.subjects.all() 47 | serializer = SubjectSerializer(subjects, many=True) 48 | return Response(serializer.data) 49 | 50 | @action(detail=False, methods=['get']) 51 | def me(self, request): 52 | if request.user.is_authenticated: 53 | serializer = UserSerializer(request.user) 54 | return Response(serializer.data, status=status.HTTP_200_OK) 55 | else: 56 | return Response(status=status.HTTP_401_UNAUTHORIZED) 57 | 58 | @action(detail=False, methods=['post']) 59 | def update_password(self, request): 60 | if request.user.is_authenticated: 61 | user = request.user 62 | old_password = request.data.get('old_password') 63 | new_password = request.data.get('new_password') 64 | confirm_password = request.data.get('confirm_password') 65 | 66 | if new_password != confirm_password: 67 | return Response({'detail': 'Password does not match.'}, status=status.HTTP_400_BAD_REQUEST) 68 | if not user.check_password(old_password): 69 | return Response({'detail': 'Old password is incorrect.'}, status=status.HTTP_400_BAD_REQUEST) 70 | user.set_password(new_password) 71 | user.save() 72 | return Response({"detail": "Password successfully updated"}, status=status.HTTP_200_OK) 73 | 74 | def get_permissions(self): 75 | if self.action in ['update_password', 'me', 'subjects']: 76 | self.permission_classes = [IsTeacher] 77 | return super().get_permissions() 78 | -------------------------------------------------------------------------------- /apps/attendance/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from apps.authentication.models import User 3 | import csv 4 | import json 5 | 6 | class Group(models.Model): 7 | name = models.CharField(max_length=50) 8 | 9 | def __str__(self) -> str: 10 | return self.name 11 | 12 | class Meta: 13 | ordering = ['name'] 14 | 15 | class Student(models.Model): 16 | first_name = models.CharField(max_length=50) 17 | last_name = models.CharField(max_length=50) 18 | email = models.EmailField(unique=True) 19 | group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='students') 20 | 21 | def fetch_attendance(self): 22 | return Attendance.objects.filter(student=self) 23 | 24 | def load_students_from_csv(self, file): 25 | with open('student.csv', 'r') as f: 26 | reader = csv.reader(f).decode('utf-8') 27 | for row in reader: 28 | group = Group.objects.get_or_create(name=row[3]) 29 | Student.objects.create(first_name=row[0], last_name=row[1], email=row[2], group=group) 30 | 31 | def load_students_from_json(self): 32 | with open('ajou.json', 'r') as f: 33 | for i in json.load(f): 34 | group = Group.objects.get(name=i['group']) 35 | Student.objects.create(first_name=i['full_name'].split()[1], last_name=i['full_name'].split()[0], email=(str(i['full_id'])+"@ajou.uz"), group=group) 36 | 37 | @property 38 | def get_attendances(self): 39 | return self.attendance_reports.all() 40 | 41 | @property 42 | def get_absents_and_lates(self): 43 | return self.attendance_reports.filter(models.Q(status='absent') | models.Q(status='late')) 44 | 45 | @property 46 | def get_subjects(self): 47 | return self.group.subjects.all() 48 | 49 | class Meta: 50 | verbose_name = 'Student' 51 | verbose_name_plural = 'Students' 52 | ordering = ['last_name', 'first_name'] 53 | 54 | def __str__(self) -> str: 55 | return self.first_name + ' ' + self.last_name + ' - ' + self.group.name 56 | 57 | 58 | class Subject(models.Model): 59 | name = models.CharField(max_length=50) 60 | teacher = models.ForeignKey(User, related_name='subjects', on_delete=models.CASCADE) 61 | group = models.ManyToManyField(Group, related_name='subjects') 62 | slug = models.SlugField(max_length=50) 63 | 64 | def __str__(self) -> str: 65 | return self.name 66 | 67 | 68 | class Satus(models.TextChoices): 69 | PRESENT = 'present', 'Present' 70 | ABSENT = 'absent', 'Absent' 71 | LATE = 'late', 'Late' 72 | 73 | 74 | class Attendance(models.Model): 75 | subject = models.ForeignKey(Subject, on_delete=models.CASCADE, related_name='attendance') 76 | date = models.DateField() 77 | created_at = models.DateTimeField(auto_now_add=True) 78 | updated_at = models.DateTimeField(auto_now=True) 79 | 80 | def __str__(self) -> str: 81 | return self.subject.name + ' - ' + self.date.strftime('%d-%m-%Y') 82 | 83 | class Meta: 84 | ordering = ['-date'] 85 | 86 | 87 | class AttendanceReport(models.Model): 88 | attendance = models.ForeignKey(Attendance, on_delete=models.CASCADE, related_name='reports') 89 | student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='attendance_reports') 90 | status = models.CharField(choices=Satus.choices, max_length=10, default=Satus.ABSENT) 91 | created_at = models.DateTimeField(auto_now_add=True) 92 | updated_at = models.DateTimeField(auto_now=True) 93 | 94 | class Meta: 95 | ordering = ['student__last_name', 'student__first_name'] 96 | 97 | def __str__(self) -> str: 98 | return self.student.first_name + ' ' + self.student.last_name + ' - ' + self.status + ' - ' + self.attendance.subject.name + ' - ' + self.attendance.date.strftime('%d-%m-%Y') -------------------------------------------------------------------------------- /apps/attendance/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from apps.attendance.models import Group, Subject, Student, Attendance, AttendanceReport, Satus 3 | 4 | 5 | class GroupSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Group 8 | fields = '__all__' 9 | 10 | 11 | class SubjectSerializer(serializers.ModelSerializer): 12 | group = GroupSerializer(many=True) 13 | teacher = serializers.SerializerMethodField() 14 | 15 | class Meta: 16 | model = Subject 17 | fields =['id', 'name','teacher', 'group', 'slug'] 18 | 19 | def get_teacher(self, obj): 20 | return { 21 | "id": obj.teacher.id, 22 | "name": obj.teacher.first_name + " " + obj.teacher.last_name 23 | } 24 | 25 | 26 | 27 | class StudentSerializer(serializers.ModelSerializer): 28 | group = GroupSerializer() 29 | 30 | class Meta: 31 | model = Student 32 | fields = '__all__' 33 | 34 | def create(self, validated_data): 35 | group = validated_data.pop('group') 36 | group = Group.objects.get(id=group['name']) 37 | student = Student.objects.create(group=group, **validated_data) 38 | return student 39 | 40 | 41 | class AttendanceSerializer(serializers.ModelSerializer): 42 | subject = serializers.SerializerMethodField() 43 | 44 | class Meta: 45 | model = Attendance 46 | fields = ['id', 'subject', 'date', 'created_at', 'updated_at'] 47 | read_only_fields = ['created_at', 'updated_at'] 48 | 49 | def get_subject(self, obj): 50 | subject = {} 51 | subject['id'] = obj.subject.id 52 | subject['name'] = obj.subject.name 53 | return subject 54 | 55 | def create(self, validated_data): 56 | subject_id = validated_data['subject_id'] 57 | date = validated_data['date'] 58 | if not Subject.objects.filter(id=subject_id).exists(): 59 | raise serializers.ValidationError("Subject doesn't exist") 60 | if Attendance.objects.filter(subject_id=subject_id, date=date).exists(): 61 | raise serializers.ValidationError("Attendance date already exists") 62 | if Subject.objects.get(id=subject_id).teacher != self.context['request'].user: 63 | raise serializers.ValidationError("You are not the teacher of this subject") 64 | attendance = Attendance.objects.create(**validated_data) 65 | return attendance 66 | 67 | 68 | class AttendanceReportSerializer(serializers.ModelSerializer): 69 | attendance = serializers.SerializerMethodField() 70 | 71 | class Meta: 72 | model = AttendanceReport 73 | fields = ['id', 'attendance', 'student', 'status', 'created_at', 'updated_at'] 74 | read_only_fields = ['created_at', 'updated_at'] 75 | 76 | def get_attendance(self, obj): 77 | attendance = {} 78 | attendance['id'] = obj.attendance.id 79 | attendance['date'] = obj.attendance.date 80 | attendance['subject'] = { 81 | 'id': obj.attendance.subject.id, 82 | 'name': obj.attendance.subject.name 83 | } 84 | return attendance 85 | 86 | def create(self, validated_data): 87 | attendance_id = validated_data['attendance_id'] 88 | student = validated_data['student'] 89 | if not Attendance.objects.filter(id=attendance_id).exists(): 90 | raise serializers.ValidationError("Attendance doesn't exist") 91 | if not Student.objects.filter(id=student.id).exists(): 92 | raise serializers.ValidationError("Student doesn't exist") 93 | if AttendanceReport.objects.filter(attendance_id=attendance_id, student_id=student.id).exists(): 94 | raise serializers.ValidationError("Attendance already taken") 95 | if Attendance.objects.get(id=attendance_id).subject.teacher != self.context['request'].user: 96 | raise serializers.ValidationError("You are not the teacher of this subject") 97 | x = Attendance.objects.get(id=attendance_id).subject.group.all() 98 | if student.group not in x: 99 | raise serializers.ValidationError("Student doesn't belong to this group") 100 | attendance_report = AttendanceReport.objects.create(**validated_data) 101 | return attendance_report 102 | 103 | def update(self, instance, validated_data): 104 | instance.status = validated_data.get('status', instance.status) 105 | instance.save() 106 | return instance 107 | 108 | 109 | class AttendanceReportViewSerializer(serializers.ModelSerializer): 110 | attendance = serializers.SerializerMethodField() 111 | student = StudentSerializer() 112 | 113 | class Meta: 114 | model = AttendanceReport 115 | fields = ['id', 'attendance', 'student', 'status', 'created_at', 'updated_at'] 116 | read_only_fields = ['created_at', 'updated_at'] 117 | 118 | def get_attendance(self, obj): 119 | attendance = {} 120 | attendance['id'] = obj.attendance.id 121 | attendance['date'] = obj.attendance.date 122 | attendance['subject'] = { 123 | 'id': obj.attendance.subject.id, 124 | 'name': obj.attendance.subject.name 125 | } 126 | return attendance -------------------------------------------------------------------------------- /apps/attendance/views.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from rest_framework import viewsets, status, response 3 | from rest_framework.decorators import action 4 | from apps.attendance.serializers import StudentSerializer, GroupSerializer, SubjectSerializer, AttendanceSerializer, AttendanceReportSerializer, AttendanceReportViewSerializer 5 | from apps.attendance.models import Student, Group, Subject, Attendance, AttendanceReport 6 | from apps.attendance.permissions import IsTeacher 7 | from rest_framework.permissions import IsAuthenticatedOrReadOnly, IsAuthenticated, IsAdminUser 8 | 9 | 10 | class StudentViewSet(viewsets.ModelViewSet): 11 | serializer_class = StudentSerializer 12 | queryset = Student.objects.all() 13 | http_method_names: List[str] = ['get', 'head', 'options'] 14 | 15 | @action(detail=True, methods=['get']) 16 | def attendances(self, request, pk=None): 17 | student = self.get_object() 18 | attendances = student.attendance_reports.all() 19 | status = request.query_params.get('status', None) 20 | if status is not None: 21 | attendances = attendances.filter(status=status) 22 | else: 23 | attendances = attendances.all() 24 | page = self.paginate_queryset(attendances) 25 | if page is not None: 26 | serializer = AttendanceReportViewSerializer(page, many=True) 27 | return self.get_paginated_response(serializer.data) 28 | serializer = AttendanceReportViewSerializer(attendances, many=True) 29 | return response.Response(serializer.data, status=status.HTTP_200_OK) 30 | 31 | 32 | class GroupViewSet(viewsets.ModelViewSet): 33 | serializer_class = GroupSerializer 34 | queryset = Group.objects.all() 35 | http_method_names: List[str] = ['get', 'head', 'options'] 36 | 37 | @action(detail=True, methods=['get']) 38 | def students(self, request, pk): 39 | group = self.get_object() 40 | students = Student.objects.filter(group=group) 41 | page = self.paginate_queryset(students) 42 | if page is not None: 43 | serializer = StudentSerializer(page, many=True) 44 | return self.get_paginated_response(serializer.data) 45 | serializer = StudentSerializer(students, many=True) 46 | return response.Response(serializer.data, status=status.HTTP_200_OK) 47 | 48 | @action(detail=True, methods=['get']) 49 | def subjects(self, request, pk): 50 | group = self.get_object() 51 | subjects = Subject.objects.filter(group__name=group.name) 52 | page = self.paginate_queryset(subjects) 53 | if page is not None: 54 | serializer = SubjectSerializer(page, many=True) 55 | return self.get_paginated_response(serializer.data) 56 | serializer = SubjectSerializer(subjects, many=True) 57 | return response.Response(serializer.data, status=status.HTTP_200_OK) 58 | 59 | 60 | class SubjectViewSet(viewsets.ModelViewSet): 61 | serializer_class = SubjectSerializer 62 | queryset = Subject.objects.all() 63 | http_method_names: List[str] = ['get', 'head', 'options'] 64 | permission_classes = [IsAdminUser] 65 | 66 | 67 | class AttendanceViewSet(viewsets.ModelViewSet): 68 | serializer_class = AttendanceSerializer 69 | queryset = Attendance.objects.all().order_by('-date') 70 | permission_classes = [IsAuthenticated] 71 | http_method_names: List[str] = ['get', 'head', 'options', 'post'] 72 | 73 | @action(detail=True, methods=['get']) 74 | def reports(self, request, pk): 75 | attendance = self.get_object() 76 | guruh = request.GET.get('group') 77 | if guruh is not None: 78 | queryset = attendance.reports.filter(student__group__id=guruh) 79 | else: 80 | queryset = attendance.reports.all() 81 | page = self.paginate_queryset(queryset) 82 | if page is not None: 83 | serializer = AttendanceReportSerializer(queryset, many=True) 84 | return self.get_paginated_response(serializer.data) 85 | serializer = AttendanceReportSerializer(queryset, many=True) 86 | return response.Response(serializer.data, status=status.HTTP_200_OK) 87 | 88 | @action(detail=False, methods=['get']) 89 | def date(self, request): 90 | day = request.GET.get('day') 91 | if day: 92 | day = day.split('-') 93 | attendance = Attendance.objects.filter( 94 | subject__teacher = request.user, 95 | date__year = int(day[0]), 96 | date__month = int(day[1]), 97 | date__day = int(day[2]) 98 | ) 99 | if attendance: 100 | return response.Response([{'subject_id': i.subject.id, 'attendance_id': i.id} for i in attendance]) 101 | return response.Response({'detail': 'Attendance not found'}) 102 | return response.Response({'detail': 'Attendance not found'}) 103 | 104 | def perform_create(self, serializer): 105 | if self.request.user.is_authenticated: 106 | serializer.save(subject_id=self.request.data['subject']) 107 | 108 | 109 | class AttendanceReportViewSet(viewsets.ModelViewSet): 110 | serializer_class = AttendanceReportSerializer 111 | queryset = AttendanceReport.objects.all().order_by('-attendance__date') 112 | http_method_names: List[str] = ['get', 'put', 'patch', 'head', 'options', 'post'] 113 | permission_classes = [IsAuthenticatedOrReadOnly] 114 | 115 | def perform_create(self, serializer): 116 | if self.request.user.is_authenticated: 117 | serializer.save(attendance_id=self.request.data['attendance']) -------------------------------------------------------------------------------- /config/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from datetime import timedelta 3 | from dotenv import load_dotenv 4 | import os 5 | 6 | # Load environment variables from .env file 7 | load_dotenv() 8 | 9 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 10 | BASE_DIR = Path(__file__).resolve().parent.parent 11 | 12 | 13 | # Quick-start development settings - unsuitable for production 14 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 15 | 16 | # SECURITY WARNING: keep the secret key used in production secret! 17 | SECRET_KEY = os.environ.get('SECRET_KEY') 18 | 19 | # SECURITY WARNING: don't run with debug turned on in production! 20 | DEBUG = os.environ.get('DEBUG', False) 21 | 22 | ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(' ') 23 | 24 | 25 | # Application definition 26 | 27 | INSTALLED_APPS = [ 28 | 'django.contrib.admin', 29 | 'django.contrib.auth', 30 | 'django.contrib.contenttypes', 31 | 'django.contrib.sessions', 32 | 'django.contrib.messages', 33 | 'django.contrib.staticfiles', 34 | 35 | # Third party apps 36 | 'rest_framework', 37 | 'rest_framework.authtoken', 38 | 'corsheaders', 39 | 'django_filters', 40 | 'django_celery_beat', 41 | 'rest_framework_swagger', 42 | 43 | # Local apps 44 | 'apps.attendance.apps.AttendanceConfig', 45 | 'apps.authentication.apps.AuthenticationConfig', 46 | 'apps.dailystat.apps.DailystatConfig', 47 | 48 | ] 49 | 50 | MIDDLEWARE = [ 51 | 'django.middleware.security.SecurityMiddleware', 52 | 'django.contrib.sessions.middleware.SessionMiddleware', 53 | 'django.middleware.common.CommonMiddleware', 54 | 'django.middleware.csrf.CsrfViewMiddleware', 55 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 56 | 'django.contrib.messages.middleware.MessageMiddleware', 57 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 58 | # 3rd party 59 | 'corsheaders.middleware.CorsMiddleware', 60 | ] 61 | 62 | ROOT_URLCONF = 'config.urls' 63 | 64 | TEMPLATES = [ 65 | { 66 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 67 | 'DIRS': [], 68 | 'APP_DIRS': True, 69 | 'OPTIONS': { 70 | 'context_processors': [ 71 | 'django.template.context_processors.debug', 72 | 'django.template.context_processors.request', 73 | 'django.contrib.auth.context_processors.auth', 74 | 'django.contrib.messages.context_processors.messages', 75 | ], 76 | 'libraries' : { 77 | 'staticfiles': 'django.templatetags.static', 78 | } 79 | }, 80 | }, 81 | ] 82 | 83 | WSGI_APPLICATION = 'config.wsgi.application' 84 | 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 88 | 89 | if DEBUG: 90 | DATABASES = { 91 | 'default': { 92 | 'ENGINE': 'django.db.backends.sqlite3', 93 | 'NAME': BASE_DIR / 'db.sqlite3', 94 | } 95 | } 96 | else: 97 | DATABASES = { 98 | 'default': { 99 | 'ENGINE': 'django.db.backends.postgresql', 100 | 'NAME': os.environ.get('DB_NAME'), 101 | 'USER': os.environ.get('DB_USER'), 102 | 'PASSWORD': os.environ.get('DB_PASSWORD'), 103 | 'HOST': os.environ.get('DB_HOST'), 104 | 'PORT': os.environ.get('DB_PORT'), 105 | } 106 | } 107 | 108 | # Password validation 109 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 110 | 111 | AUTH_PASSWORD_VALIDATORS = [ 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 117 | }, 118 | { 119 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 120 | }, 121 | { 122 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 123 | }, 124 | ] 125 | 126 | 127 | # Internationalization 128 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 129 | 130 | LANGUAGE_CODE = 'en-us' 131 | 132 | TIME_ZONE = 'Asia/Tashkent' 133 | 134 | USE_I18N = True 135 | 136 | USE_L10N = True 137 | 138 | USE_TZ = True 139 | 140 | 141 | # Static files (CSS, JavaScript, Images) 142 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 143 | 144 | STATIC_URL = '/static/' 145 | STATICFILES_DIRS = ['staticfiles'] 146 | STATIC_ROOT = BASE_DIR / 'static' 147 | 148 | # Default primary key field type 149 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 150 | 151 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 152 | 153 | 154 | AUTH_USER_MODEL = 'authentication.User' 155 | AUTHENTICATION_BACKENDS = ['apps.authentication.backends.EmailBackend'] 156 | 157 | 158 | REST_FRAMEWORK = { 159 | "DEFAULT_AUTHENTICATION_CLASSES": ( 160 | "rest_framework.authentication.TokenAuthentication", 161 | "rest_framework.authentication.BasicAuthentication", 162 | "rest_framework.authentication.SessionAuthentication", 163 | "rest_framework_simplejwt.authentication.JWTAuthentication", 164 | ), 165 | "DEFAULT_PAGINATION_CLASS": 'apps.attendance.pagination.CustomPagination', 166 | 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' 167 | } 168 | 169 | REST_USE_JWT = True 170 | JWT_AUTH_COOKIE = 'jwt-auth' 171 | 172 | 173 | SIMPLE_JWT = { 174 | 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=3000), 175 | 'REFRESH_TOKEN_LIFETIME': timedelta(days=10), 176 | 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', 177 | 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=3000), 178 | 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), 179 | } 180 | 181 | if DEBUG: 182 | CORS_ORIGIN_ALLOW_ALL = True 183 | else: 184 | CORS_ORIGIN_ALLOW_ALL = False 185 | CORS_ORIGIN_WHITELIST = os.environ.get('CORS_ORIGIN_WHITELIST', '').split(' ') 186 | 187 | CELERY_TIMEZONE = 'Asia/Tashkent' 188 | CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' 189 | 190 | if DEBUG: 191 | CELERY_BROKER_URL = 'redis://localhost:6379' 192 | CELERY_RESULT_BACKEND = 'redis://localhost:6379' 193 | else: 194 | CELERY_BROKER_URL = os.environ.get('REDIS_URL') 195 | CELERY_RESULT_BACKEND = os.environ.get('REDIS_URL') 196 | 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![The Well App](https://malikoff.uz/media/post-images/Blue_Ocean_Photo_Summer_Instagram_Post.gif) 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | --- 14 | 15 | ### What does this application do! 🚀 16 | 17 | - It is backend part of student attendance system for. 18 | - You can use this application for any kind of educational fields 19 | - ios/android: coming soon 👀 20 | --- 21 | 22 |
23 | 24 |
25 | 26 | **[ABOUT PROJECT](https://github.com/sevbo2003/student-attendance-system#what-does-this-application-do--) • 27 | [TECH STACK](https://github.com/sevbo2003/student-attendance-system#built-with) • 28 | [GETTING STARTED](https://github.com/sevbo2003/student-attendance-system#getting-started) • 29 | [INSTALLATION](https://github.com/sevbo2003/student-attendance-system#installation) • 30 | [USAGE](https://github.com/sevbo2003/student-attendance-system#usage) • 31 | [CONTRIBUTING](https://github.com/sevbo2003/student-attendance-system#contributing) • 32 | [LICENSE](https://github.com/sevbo2003/student-attendance-system#license)** 33 | 34 |
35 | 36 | --- 37 |
38 | 39 | # Built With 40 | - ![Python](https://img.shields.io/badge/python-3670A0?style=for-the-badge&logo=python&logoColor=ffdd54) 41 | - [![Django][Django]](https://www.djangoproject.com/) 42 | - ![DjangoREST](https://img.shields.io/badge/DJANGO-REST-ff1709?style=for-the-badge&logo=django&logoColor=white&color=ff1709&labelColor=gray) 43 | - [![Postgres][Postgres.db]](https://www.postgresql.org/) 44 | - [![Redis][Redis]](https://redis.io/) 45 | - ![AWS](https://img.shields.io/badge/AWS-%23FF9900.svg?style=for-the-badge&logo=amazon-aws&logoColor=white) 46 | - ![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white) 47 | - ![Gunicorn](https://img.shields.io/badge/gunicorn-%298729.svg?style=for-the-badge&logo=gunicorn&logoColor=white) 48 | - ![Nginx](https://img.shields.io/badge/nginx-%23009639.svg?style=for-the-badge&logo=nginx&logoColor=white) 49 | 50 | 56 | 57 | 58 | --- 59 |
60 | 61 | 62 | # Getting Started 63 | 64 | This is an example of how you may give instructions on setting up your project locally. 65 | To get a local copy up and running follow these simple example steps. 66 | 67 | # Prerequisites 68 | 69 | You need `linux` machine and Python package manager(`pip`) to run the project. 70 | * pip 71 | ```bash 72 | sudo apt install python3-pip 73 | ``` 74 | * git 75 | ```bash 76 | sudo apt install git 77 | ``` 78 | 79 | # Installation 80 | 81 | 1. Clone the repo 82 | ```sh 83 | git clone https://github.com/sevbo2003/student-attendance-system.git 84 | ``` 85 | 2. Go to project directory 86 | ```sh 87 | cd student-attendance-system 88 | ``` 89 | 3. Create virtual environment 90 | ```sh 91 | python3 -m venv venv 92 | ``` 93 | 4. Activate virtual environment 94 | ```sh 95 | source venv/bin/activate 96 | ``` 97 | 5. Install requirements 98 | ```sh 99 | pip install -r requirements.txt 100 | ``` 101 | 6. Create `.env` file and fill it with your credentials like `.env.example` 102 | ```sh 103 | touch .env 104 | ``` 105 | ```python 106 | # .env 107 | DEBUG=True # or False 108 | SECRET_KEY=your_secret_key 109 | ALLOWED_HOSTS=* 110 | 111 | # db 112 | DB_NAME= 113 | DB_USER= 114 | DB_PASSWORD= 115 | DB_HOST= 116 | DB_PORT= 117 | 118 | # redis 119 | REDIS_URL=redis://user:password@host:port 120 | 121 | # cors 122 | CORS_ORIGIN_WHITELIST= 123 | ``` 124 | 7. Run migrations and migrate 125 | ```sh 126 | python manage.py makemigrations 127 | ``` 128 | ```sh 129 | python manage.py migrate 130 | ``` 131 | 8. Create superuser 132 | ```sh 133 | python manage.py createsuperuser 134 | ``` 135 | 9. Run server 136 | ```sh 137 | python manage.py runserver 138 | ``` 139 | 10. Install Redis in your machine and run it 140 | ```sh 141 | sudo apt install redis-server 142 | ``` 143 | ```sh 144 | redis-server 145 | ``` 146 | 11. Run celery worker 147 | ```sh 148 | celery -A config worker -l info 149 | ``` 150 | 12. Run celery beat 151 | ```sh 152 | celery -A config beat -l info 153 | ``` 154 | 13. All done! Now you can use the application 155 | 156 | 157 | --- 158 |
159 | 160 | # Usage 161 | Here you can find important APIs to use the application 162 | 163 | - Get token for user 164 | ```sh 165 | POST /api/token/ 166 | ``` 167 | ```json 168 | { 169 | "email": "your_email", 170 | "password": "your_password" 171 | } 172 | ``` 173 | - Get students info 174 | ```sh 175 | GET /accounts/users/ 176 | ``` 177 | - Get teachers info 178 | ```sh 179 | GET /accounts/teachers/ 180 | ``` 181 | - Get groups info 182 | ```sh 183 | GET /attendance/groups/ 184 | ``` 185 | - Get group subjects info 186 | ```sh 187 | GET /attendance/group/{group_id}/subjects/ 188 | ``` 189 | - Get group students info 190 | ```sh 191 | GET /attendance/group/{group_id}/students/ 192 | ``` 193 | - Get attendance info 194 | ```sh 195 | GET /attendance/attendance-report/ 196 | ``` 197 | - Get daily attendance status 198 | ```sh 199 | GET /dailystat/ 200 | ``` 201 | 202 | --- 203 |
204 | 205 | # Contributing 206 | 207 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 208 | 209 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 210 | Don't forget to give the project a star! Thanks again! 211 | 212 | 1. Fork the Project 213 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 214 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 215 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 216 | 5. Open a Pull Request 217 | 218 | --- 219 |
220 | 221 | 222 | # License 223 | 224 | Distributed under the MIT License. See `LICENSE` for more information. 225 | 226 | 227 | --- 228 | 229 |
230 | 231 | # 💛 232 | Made with ❤️ by [Abdusamad](https://github.com/sevbo2003) 233 | - Reminder that *you are great, you are enough, and your presence is valued.* 234 | 235 | 236 | 237 | 238 | 239 | 240 | [product-screenshot]: images/screenshot.png 241 | [Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white 242 | [Next-url]: https://nextjs.org/ 243 | [React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB 244 | [React-url]: https://reactjs.org/ 245 | [Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white 246 | [Bootstrap-url]: https://getbootstrap.com 247 | [Postgres.db]: https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white 248 | [Redis]: https://img.shields.io/badge/redis-%23DD0031.svg?style=for-the-badge&logo=redis&logoColor=white 249 | [Django]: https://img.shields.io/badge/django-%23092E20.svg?style=for-the-badge&logo=django&logoColor=white --------------------------------------------------------------------------------