├── .dockerignore ├── .flake8 ├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── accounts ├── __init__.py ├── admin.py ├── api │ ├── __init__.py │ ├── serializers.py │ ├── urls.py │ └── views.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_user_is_supervisor.py │ ├── 0003_auto_20230730_1817.py │ └── __init__.py ├── models.py └── urls.py ├── core ├── __init__.py ├── asgi.py ├── permissions.py ├── settings.py ├── urls.py └── wsgi.py ├── default.conf ├── docker-compose-prod.yml ├── docker-compose.yml ├── envs └── .env.sample ├── manage.py ├── requirements.txt └── timetable ├── __init__.py ├── admin.py ├── api ├── __init__.py ├── serializers.py ├── urls.py └── views.py ├── apps.py ├── filters.py ├── migrations ├── 0001_initial.py ├── 0002_alter_freesectionteacher_free_section.py ├── 0003_alter_freesectionteacher_free_section.py ├── 0004_alter_freesectionteacher_free_section.py ├── 0005_remove_freesectionteacher_free_section_class_and_more.py ├── 0006_remove_freesectionteacher_free_section_class_and_more.py ├── 0007_auto_20230730_1239.py ├── 0008_auto_20230802_1629.py └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py /.dockerignore: -------------------------------------------------------------------------------- 1 | venv/ -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | migrations, 4 | __pycache__, 5 | manage.py, 6 | settings.py, 7 | venv, 8 | env, 9 | max-line-length = 79 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # IPython 77 | profile_default/ 78 | ipython_config.py 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | # Pyre type checker 114 | .pyre/ 115 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "redoc" 4 | ] 5 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim-buster 2 | 3 | ENV PYTHONDONTWRITEBYTECODE=1 4 | ENV PYTHONUNBUFFERED=1 5 | 6 | WORKDIR /app 7 | 8 | COPY requirements.txt /app/ 9 | 10 | RUN pip3 install --upgrade pip 11 | RUN pip3 install -r requirements.txt 12 | 13 | COPY ./core /app 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Amir Zahedi 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TimeTable 2 | 3 | The TimeTable repository is a system that facilitates the scheduling of classes for teachers and supervisors. It distinguishes between two types of users: teachers and supervisors, each with their respective APIs for registration. The users are differentiated by two boolean attributes: `is_teacher` and `is_supervisor`. 4 | 5 | ## User Types 6 | 7 | 1. **Teachers**: Teachers are individuals who offer their availability to conduct classes. They have the following API for registration: 8 | - `/api/register_teacher`: Endpoint for teacher registration. 9 | 10 | 2. **Supervisors**: Supervisors are responsible for managing the scheduling of classes. They have the following API for registration: 11 | - `/api/register_supervisor`: Endpoint for supervisor registration. 12 | 13 | Once registered, supervisors can set the `teacher_id` for each teacher using the following API: 14 | - `/api/set_teacher_id`: API to set the `teacher_id` for a teacher. 15 | 16 | ## Time Sections 17 | 18 | The TimeTable system consists of two main types of timetables. 19 | 20 | ### 1. Free Time Sections Table 21 | 22 | Time sections are the blocks of time during which a teacher is available to conduct classes. These sections are defined for each teacher and can be utilized by supervisors to schedule classes. The time sections are scheduled every day of the week and occur every 45 minutes, starting from 9:00 - 9:45 and continuing until 21:00 - 21:45. 23 | 24 | #### Class Model 25 | 26 | The class model includes the following fields: 27 | - `student`: Represents the student enrolled in the class. 28 | - `platform`: Indicates the platform where the class will be conducted. 29 | - `session`: Specifies additional session details. 30 | 31 | ### 2. Main Timetable 32 | 33 | The main timetable is a comprehensive view accessible to supervisors, displaying the scheduled classes for each teacher. It allows supervisors to quickly see which teachers are teaching during a specific time section or which teachers have free time during a particular period. 34 | 35 | --- 36 | 37 | By following these guidelines, the TimeTable project becomes an efficient tool for managing class schedules and facilitating communication between teachers and supervisors. With its user-friendly APIs and clear organization of time sections, the project aims to streamline the scheduling process and enhance the overall teaching experience. 38 | -------------------------------------------------------------------------------- /accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrzhd/TimeTable/66877939a28dd2d5c752768704e582726958a423/accounts/__init__.py -------------------------------------------------------------------------------- /accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import User 3 | from django.contrib.auth.admin import UserAdmin 4 | 5 | 6 | class UserAdminConfig(UserAdmin): 7 | model = User 8 | search_fields = ("email", "national_code", "personal_id",) 9 | list_filter = ("email", "national_code", "is_active", "is_staff", "is_teacher", "is_supervisor", "is_consultant", "personal_id",) 10 | ordering = ("-created_date",) 11 | list_display = ("email", "national_code", "is_active", "is_staff", "is_teacher", "is_supervisor", "is_consultant", "personal_id",) 12 | fieldsets = ( 13 | ("Authentication", {"fields": ("email", "national_code", "personal_id")}), 14 | ("Permissions", {"fields": ("is_staff", "is_active", "is_teacher", "is_supervisor", "is_consultant")}), 15 | ( 16 | "Group Permissions", 17 | { 18 | "fields": ( 19 | "groups", 20 | "user_permissions", 21 | ) 22 | }, 23 | ), 24 | ("Important dates", {"fields": ("last_login",)}), 25 | ) 26 | add_fieldsets = ( 27 | ( 28 | None, 29 | { 30 | "classes": ("wide",), 31 | "fields": ( 32 | "email", 33 | "national_code", 34 | "phone", 35 | "password1", 36 | "password2", 37 | "is_active", 38 | "is_staff", 39 | "is_teacher", 40 | "is_supervisor", 41 | "is_consultant", 42 | "personal_id", 43 | ), 44 | }, 45 | ), 46 | ) 47 | 48 | 49 | admin.site.register(User, UserAdminConfig) 50 | -------------------------------------------------------------------------------- /accounts/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrzhd/TimeTable/66877939a28dd2d5c752768704e582726958a423/accounts/api/__init__.py -------------------------------------------------------------------------------- /accounts/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from accounts.models import User 3 | 4 | 5 | class UserCheckSerializer(serializers.ModelSerializer): 6 | 7 | class Meta: 8 | model = User 9 | fields = [ 10 | "national_code", 11 | ] 12 | 13 | class TeacherRegisterSerializer(serializers.ModelSerializer): 14 | """ 15 | Registration serializer with password checkup 16 | """ 17 | password = serializers.CharField(max_length=68, min_length=6, write_only=True) 18 | password1 = serializers.CharField(max_length=68, min_length=6, write_only=True) 19 | 20 | class Meta: 21 | model = User 22 | fields = [ 23 | "email", 24 | "phone", 25 | "national_code", 26 | "first_name", 27 | "last_name", 28 | "password", 29 | "password1", 30 | ] 31 | 32 | def validate(self, attr): 33 | attr["is_teacher"] = True 34 | if attr["password"] != attr["password1"]: 35 | raise serializers.ValidationError({"details": "Passwords does not match"}) 36 | attr.pop("password1") 37 | return attr 38 | 39 | def create(self, validated_data): 40 | return User.objects.create_user(**validated_data) 41 | 42 | class SupervisorRegisterSerializer(serializers.ModelSerializer): 43 | """ 44 | Registration serializer with password checkup 45 | """ 46 | password = serializers.CharField(max_length=68, min_length=6, write_only=True) 47 | password1 = serializers.CharField(max_length=68, min_length=6, write_only=True) 48 | 49 | class Meta: 50 | model = User 51 | fields = [ 52 | "email", 53 | "phone", 54 | "national_code", 55 | "first_name", 56 | "last_name", 57 | "password", 58 | "password1", 59 | ] 60 | 61 | def validate(self, attr): 62 | attr["is_supervisor"] = True 63 | if attr["password"] != attr["password1"]: 64 | raise serializers.ValidationError({"details": "Passwords does not match"}) 65 | attr.pop("password1") 66 | return attr 67 | 68 | def create(self, validated_data): 69 | return User.objects.create_user(**validated_data) 70 | 71 | class ConsultantRegisterSerializer(serializers.ModelSerializer): 72 | """ 73 | Registration serializer with password checkup 74 | """ 75 | password = serializers.CharField(max_length=68, min_length=6, write_only=True) 76 | password1 = serializers.CharField(max_length=68, min_length=6, write_only=True) 77 | 78 | class Meta: 79 | model = User 80 | fields = [ 81 | "email", 82 | "phone", 83 | "national_code", 84 | "first_name", 85 | "last_name", 86 | "password", 87 | "password1", 88 | ] 89 | 90 | def validate(self, attr): 91 | attr["is_consultant"] = True 92 | if attr["password"] != attr["password1"]: 93 | raise serializers.ValidationError({"details": "Passwords does not match"}) 94 | attr.pop("password1") 95 | return attr 96 | 97 | def create(self, validated_data): 98 | return User.objects.create_user(**validated_data) 99 | 100 | class GiveUserIdSerializer(serializers.Serializer): 101 | email = serializers.EmailField() 102 | personal_id = serializers.CharField(max_length=20) 103 | 104 | def update(self, instance, validated_data): 105 | instance.personal_id = validated_data["personal_id"] 106 | instance.save() 107 | return instance -------------------------------------------------------------------------------- /accounts/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework.authtoken.views import ObtainAuthToken 3 | from rest_framework_simplejwt.views import ( 4 | TokenObtainPairView, 5 | TokenRefreshView, 6 | TokenVerifyView, 7 | ) 8 | from .views import ( 9 | UserCheckAPIView, 10 | TeacherRegisterAPIView, 11 | SupervisorRegisterAPIView, 12 | ConsultantRegisterAPIView, 13 | GiveUserIdUpdateAPIView, 14 | 15 | ) 16 | 17 | 18 | urlpatterns = [ 19 | #Register API: 20 | path("user-check/", UserCheckAPIView.as_view(), name = "user-check"), 21 | path("register-teacher/", TeacherRegisterAPIView.as_view(), name="register-teacher"), 22 | path("register-supervisor/", SupervisorRegisterAPIView.as_view(), name="register-supervisor"), 23 | path("register-consultant/", ConsultantRegisterAPIView.as_view(), name="register-consultant"), 24 | 25 | #Token API: 26 | path("token/login/", ObtainAuthToken.as_view(), name="token_obtain"), 27 | path("jwt/create/", TokenObtainPairView.as_view(), name="jwt_obtain_pair"), 28 | path("jwt/refresh/", TokenRefreshView.as_view(), name="jwt_refresh"), 29 | path("jwt/verify/", TokenVerifyView.as_view(), name="jwt_verify"), 30 | 31 | #ID Assign API:: 32 | path("give-user-id/", GiveUserIdUpdateAPIView.as_view(), name="give-user-id"), 33 | ] 34 | -------------------------------------------------------------------------------- /accounts/api/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.response import Response 2 | from rest_framework import status 3 | from rest_framework import generics 4 | from rest_framework.permissions import IsAuthenticated 5 | from core.permissions import IsConsultant, IsSupervisor 6 | from django.shortcuts import get_object_or_404 7 | from accounts.models import User 8 | from .serializers import ( 9 | TeacherRegisterSerializer, 10 | SupervisorRegisterSerializer, 11 | ConsultantRegisterSerializer, 12 | GiveUserIdSerializer, 13 | UserCheckSerializer, 14 | ) 15 | 16 | class UserCheckAPIView(generics.GenericAPIView): 17 | """ 18 | Checks whether the user is registered before or not 19 | """ 20 | serializer_class = UserCheckSerializer 21 | 22 | def post(self, request, *args, **kwargs): 23 | serializer = self.get_serializer(data=request.data) 24 | if serializer.is_valid(): 25 | national_code = serializer.validated_data.get('national_code') 26 | 27 | # Check if the user with the given national_code exists in the database 28 | try: 29 | user = User.objects.get(national_code=national_code) 30 | return Response({"national_code":f"{national_code}"}, 31 | status=status.HTTP_200_OK) 32 | except User.DoesNotExist: 33 | return Response({"message":f"User with national code of {national_code} doesn't exist!"}, 34 | status=status.HTTP_404_NOT_FOUND) 35 | 36 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 37 | 38 | class TeacherRegisterAPIView(generics.GenericAPIView): 39 | """ 40 | Creates a new teacher user with the given info and credentials 41 | """ 42 | 43 | serializer_class = TeacherRegisterSerializer 44 | 45 | def post(self, request, *args, **kwargs): 46 | 47 | serializer = TeacherRegisterSerializer(data=request.data, many=False) 48 | 49 | if serializer.is_valid(): 50 | serializer.save() 51 | return Response({"message": f"Teacher has registered successfully"}, status=status.HTTP_201_CREATED) 52 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 53 | 54 | class SupervisorRegisterAPIView(generics.GenericAPIView): 55 | """ 56 | Creates a new consultant user with the given info and credentials 57 | """ 58 | 59 | serializer_class = SupervisorRegisterSerializer 60 | 61 | def post(self, request, *args, **kwargs): 62 | 63 | serializer = SupervisorRegisterSerializer(data=request.data, many=False) 64 | 65 | if serializer.is_valid(): 66 | serializer.save() 67 | return Response({"message": f"Supervisor has registered successfully"}, status=status.HTTP_201_CREATED) 68 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 69 | 70 | class ConsultantRegisterAPIView(generics.GenericAPIView): 71 | """ 72 | Creates a new consultant user with the given info and credentials 73 | """ 74 | 75 | serializer_class = ConsultantRegisterSerializer 76 | 77 | def post(self, request, *args, **kwargs): 78 | 79 | serializer = ConsultantRegisterSerializer(data=request.data, many=False) 80 | 81 | if serializer.is_valid(): 82 | serializer.save() 83 | return Response({"message": f"Consultant has registered successfully"}, status=status.HTTP_201_CREATED) 84 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 85 | 86 | class GiveUserIdUpdateAPIView(generics.UpdateAPIView): 87 | """ 88 | Assigns an ID to a teacher 89 | """ 90 | permission_classes = [IsSupervisor, IsAuthenticated] 91 | serializer_class = GiveUserIdSerializer 92 | queryset = User.objects.all() 93 | 94 | def update(self, request, *args, **kwargs): 95 | 96 | serializer = self.get_serializer(data=request.data) 97 | 98 | if serializer.is_valid(): 99 | email = serializer.validated_data.get('email') 100 | personal_id = serializer.validated_data.get('personal_id') 101 | 102 | try: 103 | user = self.get_queryset().get(email=email) 104 | except User.DoesNotExist: 105 | return Response({'error': 'User not found.'}, status=status.HTTP_404_NOT_FOUND) 106 | 107 | user.personal_id = personal_id 108 | user.save() 109 | 110 | return Response({'message': 'The id is given to the user successfully!'}, status=status.HTTP_200_OK) 111 | 112 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 113 | -------------------------------------------------------------------------------- /accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "accounts" 7 | -------------------------------------------------------------------------------- /accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-23 06:38 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | initial = True 8 | 9 | dependencies = [ 10 | ("auth", "0012_alter_user_first_name_max_length"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="User", 16 | fields=[ 17 | ( 18 | "id", 19 | models.BigAutoField( 20 | auto_created=True, 21 | primary_key=True, 22 | serialize=False, 23 | verbose_name="ID", 24 | ), 25 | ), 26 | ("password", models.CharField(max_length=128, verbose_name="password")), 27 | ( 28 | "last_login", 29 | models.DateTimeField( 30 | blank=True, null=True, verbose_name="last login" 31 | ), 32 | ), 33 | ( 34 | "is_superuser", 35 | models.BooleanField( 36 | default=False, 37 | help_text="Designates that this user has all permissions without explicitly assigning them.", 38 | verbose_name="superuser status", 39 | ), 40 | ), 41 | ( 42 | "email", 43 | models.EmailField( 44 | max_length=254, unique=True, verbose_name="email address" 45 | ), 46 | ), 47 | ( 48 | "phone", 49 | models.CharField(max_length=11, unique=True, verbose_name="phone"), 50 | ), 51 | ("first_name", models.CharField(max_length=250)), 52 | ("last_name", models.CharField(max_length=250)), 53 | ("national_code", models.CharField(max_length=10)), 54 | ("is_staff", models.BooleanField(default=False)), 55 | ("is_active", models.BooleanField(default=True)), 56 | ("created_date", models.DateTimeField(auto_now_add=True)), 57 | ("updated_date", models.DateTimeField(auto_now=True)), 58 | ("is_consultant", models.BooleanField(default=False)), 59 | ("is_teacher", models.BooleanField(default=False)), 60 | ("teacher_id", models.CharField(blank=True, max_length=20, null=True)), 61 | ( 62 | "groups", 63 | models.ManyToManyField( 64 | blank=True, 65 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 66 | related_name="user_set", 67 | related_query_name="user", 68 | to="auth.group", 69 | verbose_name="groups", 70 | ), 71 | ), 72 | ( 73 | "user_permissions", 74 | models.ManyToManyField( 75 | blank=True, 76 | help_text="Specific permissions for this user.", 77 | related_name="user_set", 78 | related_query_name="user", 79 | to="auth.permission", 80 | verbose_name="user permissions", 81 | ), 82 | ), 83 | ], 84 | options={ 85 | "abstract": False, 86 | }, 87 | ), 88 | ] 89 | -------------------------------------------------------------------------------- /accounts/migrations/0002_user_is_supervisor.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-07-30 09:16 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0001_initial'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='is_supervisor', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /accounts/migrations/0003_auto_20230730_1817.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-07-30 14:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('accounts', '0002_user_is_supervisor'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='user', 15 | name='personal_id', 16 | field=models.CharField(blank=True, max_length=20, null=True), 17 | ), 18 | migrations.AlterField( 19 | model_name='user', 20 | name='teacher_id', 21 | field=models.CharField(max_length=10), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrzhd/TimeTable/66877939a28dd2d5c752768704e582726958a423/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import receiver 2 | from django.db.models.signals import post_save 3 | from django.contrib.auth.base_user import BaseUserManager 4 | from django.utils.translation import gettext_lazy as _ 5 | from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin 6 | from django.db import models 7 | #from timetable.models import TeachingTime 8 | 9 | 10 | class UserManager(BaseUserManager): 11 | """ 12 | Custom user model manager where email is the unique identifiers 13 | for authentication instead of usernames. 14 | """ 15 | 16 | def create_user(self, email, password, **extra_fields): 17 | """ 18 | Create and save a User with the given email and password. 19 | """ 20 | if not email: 21 | raise ValueError(_("The Email must be set")) 22 | email = self.normalize_email(email) 23 | user = self.model(email=email, **extra_fields) 24 | user.set_password(password) 25 | user.save() 26 | return user 27 | 28 | def create_superuser(self, email, password, **extra_fields): 29 | """ 30 | Create and save a SuperUser with the given email and password. 31 | """ 32 | extra_fields.setdefault("is_staff", True) 33 | extra_fields.setdefault("is_superuser", True) 34 | extra_fields.setdefault("is_active", True) 35 | 36 | if extra_fields.get("is_staff") is not True: 37 | raise ValueError(_("Superuser must have is_staff=True.")) 38 | if extra_fields.get("is_superuser") is not True: 39 | raise ValueError(_("Superuser must have is_superuser=True.")) 40 | return self.create_user(email, password, **extra_fields) 41 | 42 | 43 | class User(AbstractBaseUser, PermissionsMixin): 44 | email = models.EmailField(_("email address"), unique=True) 45 | phone = models.CharField(_("phone"), max_length=11, unique=True) 46 | first_name = models.CharField(max_length=250) 47 | last_name = models.CharField(max_length=250) 48 | national_code = models.CharField(max_length=10) 49 | teacher_id = models.CharField(max_length=10) 50 | is_staff = models.BooleanField(default=False) 51 | is_active = models.BooleanField(default=True) 52 | created_date = models.DateTimeField(auto_now_add=True) 53 | updated_date = models.DateTimeField(auto_now=True) 54 | 55 | is_supervisor = models.BooleanField(default=False) 56 | is_consultant = models.BooleanField(default=False) 57 | is_teacher = models.BooleanField(default=False) 58 | 59 | personal_id = models.CharField(max_length=20, null=True, blank=True) 60 | 61 | USERNAME_FIELD = "email" 62 | REQUIRED_FIELDS = [] 63 | 64 | objects = UserManager() 65 | 66 | def __str__(self): 67 | return self.email 68 | 69 | @property 70 | def full_name(self): 71 | return f"{self.first_name} {self.last_name}" 72 | -------------------------------------------------------------------------------- /accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | app_name = "accounts" 4 | 5 | urlpatterns = [ 6 | # template base authentication 7 | # path('', include('django.contrib.auth.urls')), 8 | # api based authentication 9 | path("api/", include("accounts.api.urls")), 10 | ] 11 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrzhd/TimeTable/66877939a28dd2d5c752768704e582726958a423/core/__init__.py -------------------------------------------------------------------------------- /core/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for core 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", "core.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /core/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import BasePermission 2 | 3 | class IsSupervisor(BasePermission): 4 | def has_permission(self, request, view): 5 | user = request.user 6 | return super().has_permission(request, view) and user.is_supervisor 7 | 8 | class IsConsultant(BasePermission): 9 | def has_permission(self, request, view): 10 | user = request.user 11 | return super().has_permission(request, view) and user.is_consultant 12 | -------------------------------------------------------------------------------- /core/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for core project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.8. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | from datetime import timedelta 15 | from decouple import config 16 | 17 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 18 | BASE_DIR = Path(__file__).resolve().parent.parent 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = config("SECRET_KEY", default="test") 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = config("DEBUG", cast=bool, default=True) 29 | 30 | ALLOWED_HOSTS = ["*"] 31 | 32 | CSRF_TRUSTED_ORIGINS = ["https://*.ngrok.io", "https://*.127.0.0.1"] 33 | 34 | # Application definition 35 | 36 | INSTALLED_APPS = [ 37 | "django.contrib.admin", 38 | "django.contrib.auth", 39 | "django.contrib.contenttypes", 40 | "django.contrib.sessions", 41 | "django.contrib.messages", 42 | "django.contrib.staticfiles", 43 | "rest_framework", 44 | "rest_framework.authtoken", 45 | "rest_framework_simplejwt", 46 | "drf_spectacular", 47 | "accounts.apps.AccountsConfig", 48 | "timetable.apps.TimetableConfig", 49 | "silk", 50 | "django_filters", 51 | ] 52 | 53 | MIDDLEWARE = [ 54 | "django.middleware.security.SecurityMiddleware", 55 | "django.contrib.sessions.middleware.SessionMiddleware", 56 | "django.middleware.common.CommonMiddleware", 57 | "django.middleware.csrf.CsrfViewMiddleware", 58 | "django.contrib.auth.middleware.AuthenticationMiddleware", 59 | "django.contrib.messages.middleware.MessageMiddleware", 60 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 61 | "silk.middleware.SilkyMiddleware", 62 | 63 | ] 64 | 65 | ROOT_URLCONF = "core.urls" 66 | 67 | TEMPLATES = [ 68 | { 69 | "BACKEND": "django.template.backends.django.DjangoTemplates", 70 | "DIRS": [], 71 | "APP_DIRS": True, 72 | "OPTIONS": { 73 | "context_processors": [ 74 | "django.template.context_processors.debug", 75 | "django.template.context_processors.request", 76 | "django.contrib.auth.context_processors.auth", 77 | "django.contrib.messages.context_processors.messages", 78 | ], 79 | }, 80 | }, 81 | ] 82 | 83 | WSGI_APPLICATION = "core.wsgi.application" 84 | 85 | 86 | # Database 87 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 88 | 89 | DATABASES = { 90 | "default": { 91 | "ENGINE": "django.db.backends.sqlite3", 92 | "NAME": BASE_DIR / "db.sqlite3", 93 | } 94 | } 95 | 96 | 97 | # Password validation 98 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 99 | 100 | AUTH_PASSWORD_VALIDATORS = [ 101 | { 102 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 103 | }, 104 | { 105 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 106 | }, 107 | { 108 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 109 | }, 110 | { 111 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 112 | }, 113 | ] 114 | 115 | 116 | # Internationalization 117 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 118 | 119 | LANGUAGE_CODE = "en-us" 120 | 121 | TIME_ZONE = "UTC" 122 | 123 | USE_TZ = True 124 | 125 | 126 | # Static files (CSS, JavaScript, Images) 127 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 128 | 129 | 130 | STATIC_ROOT = BASE_DIR / "static" 131 | STATIC_URL = "/static/" 132 | MEDIA_ROOT = BASE_DIR / "media" 133 | MEDIA_URL = "/media/" 134 | 135 | STATICFILES_DIRS = [ 136 | (BASE_DIR / "staticfiles"), 137 | ] 138 | 139 | 140 | # Default primary key field type 141 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 142 | 143 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 144 | 145 | AUTH_USER_MODEL = "accounts.User" 146 | AUTH_PROFILE_MODULE = "accounts.User" 147 | 148 | # rest framework settings 149 | REST_FRAMEWORK = { 150 | "DEFAULT_PERMISSION_CLASSES": [ 151 | "rest_framework.permissions.AllowAny", 152 | ], 153 | "DEFAULT_AUTHENTICATION_CLASSES": [ 154 | "rest_framework_simplejwt.authentication.JWTAuthentication", 155 | "rest_framework.authentication.TokenAuthentication", 156 | "rest_framework.authentication.BasicAuthentication", 157 | "rest_framework.authentication.SessionAuthentication", 158 | ], 159 | # drf_spectacular 160 | "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", 161 | 162 | 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'], 163 | } 164 | if not DEBUG: 165 | REST_FRAMEWORK.update( 166 | {"DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",)} 167 | ) 168 | 169 | SPECTACULAR_SETTINGS = { 170 | # SPECTACULAR_SETTINGS docs https://drf-spectacular.readthedocs.io/en/latest/settings.html 171 | "TITLE": "Time Table Rest API", 172 | "DESCRIPTION": "Time Table Rest API Backend Service", 173 | "VERSION": "1.0.0", 174 | "SERVE_INCLUDE_SCHEMA": False, 175 | "CONTACT": ("test"), 176 | "CONTACT": {"email": "amirzahedi0@gmail.com"}, 177 | "LICENSE": {"name": "MIT License"}, # url': 'https://license_link' 178 | # OTHER SETTINGS 179 | } 180 | 181 | # time for clear cache (OTP SYSTEM ) 182 | OTP_CODE_TIME = 180 183 | 184 | 185 | 186 | # json web token configs 187 | SIMPLE_JWT = { 188 | "ACCESS_TOKEN_LIFETIME": timedelta(days=7), 189 | "REFRESH_TOKEN_LIFETIME": timedelta(days=7), 190 | "ROTATE_REFRESH_TOKENS": False, 191 | "BLACKLIST_AFTER_ROTATION": False, 192 | "UPDATE_LAST_LOGIN": False, 193 | } 194 | 195 | # # Caching configuration1 196 | # CACHES = { 197 | # "default": { 198 | # "BACKEND": "django_redis.cache.RedisCache", 199 | # "LOCATION": "redis://redis:6379", 200 | # "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, 201 | # } 202 | # } 203 | 204 | # # celery configuration 205 | 206 | # CELERY_BROKER_URL = "redis://redis:6379/1" -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | """core URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/3.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path, include 18 | from django.conf import settings 19 | from django.conf.urls.static import static 20 | from drf_spectacular.views import ( 21 | SpectacularAPIView, 22 | SpectacularRedocView, 23 | SpectacularSwaggerView, 24 | ) 25 | from decouple import config 26 | 27 | 28 | 29 | urlpatterns = [ 30 | #admin panel 31 | path("admin/", admin.site.urls), 32 | # accounts app 33 | path("accounts/", include("accounts.urls")), 34 | # timetable app 35 | path("timetable/", include("timetable.urls")), 36 | # api authentication 37 | path("api-auth/", include("rest_framework.urls", namespace="rest_framework")), 38 | # api doc app 39 | path("api/schema/", SpectacularAPIView.as_view(), name="schema"), 40 | path("api/schema/swagger-ui/",SpectacularSwaggerView.as_view(url_name="schema"),name="swagger-ui"), 41 | path("api/schema/redoc/",SpectacularRedocView.as_view(url_name="schema"),name="redoc"), 42 | #silk 43 | path(f"{config('SILK_URL', default='silk')}/", include('silk.urls', namespace='silk')), 44 | ] 45 | 46 | if settings.DEBUG: 47 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 48 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 49 | -------------------------------------------------------------------------------- /core/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for core 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", "core.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /default.conf: -------------------------------------------------------------------------------- 1 | 2 | upstream django { 3 | server backend:8000; 4 | } 5 | server{ 6 | listen 80; 7 | #server_name example.org; 8 | #access_log /var/log/nginx/example.log; 9 | 10 | location /static/{ 11 | alias /home/app/static/; 12 | } 13 | 14 | location /media/{ 15 | alias /home/app/media/; 16 | } 17 | 18 | location /{ 19 | proxy_pass http://django 20 | proxy_set_header Host $hostl; 21 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 22 | } 23 | } -------------------------------------------------------------------------------- /docker-compose-prod.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | backend: 5 | build: . 6 | container_name: backend 7 | command: sh -c "python3 manage.py makemigrations --noinput && \ 8 | python3 manage.py migrate --noinput && \ 9 | python3 manage.py collectstatic --noinput && \ 10 | gunicorn --bind 0.0.0.0:8000 core.wsgi:application" 11 | volumes: 12 | - ./core:/app 13 | - media_volume:/usr/src/app/media 14 | expose: 15 | - "8000" 16 | depends_on: 17 | - redis 18 | - db 19 | env_file: 20 | - ./envs/prod/django/.env 21 | 22 | db: 23 | container_name: db 24 | image: postgres 25 | volumes: 26 | - postgres_data:/var/lib/postgresql/data 27 | env_file: 28 | - ./envs/prod/db/.env 29 | restart: always 30 | healthcheck: 31 | test: ['CMD-SHELL', 'pg_isready -U postgres'] 32 | interval: 10s 33 | timeout: 5s 34 | retries: 5 35 | 36 | nginx: 37 | image: nginx 38 | container_name: nginx 39 | ports: 40 | -"80:80" 41 | volumes: 42 | - ./default.conf:/etc/nginx/conf.d/default.conf 43 | - static_volumes:/home/app/static 44 | - media_volumes:/home/app/media 45 | depends_on: 46 | - backend 47 | restart: always 48 | 49 | volumes: 50 | static_volumes: 51 | media_volumes: -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | 5 | backend: 6 | build: . 7 | container_name: backend 8 | command: python manage.py runserver 0.0.0.0:8000 9 | volumes: 10 | - ./core:/app 11 | ports: 12 | - "8000:8000" 13 | depends_on: 14 | - redis 15 | - db 16 | env_file: 17 | - ./envs/prod/django/.env 18 | 19 | db: 20 | container_name: db 21 | image: postgres 22 | volumes: 23 | - postgres_data:/var/lib/postgresql/data 24 | env_file: 25 | - ./envs/prod/db/.env 26 | restart: always 27 | healthcheck: 28 | test: ['CMD-SHELL', 'pg_isready -U postgres'] 29 | interval: 10s 30 | timeout: 5s 31 | retries: 5 32 | 33 | volumes: 34 | postgres_data: -------------------------------------------------------------------------------- /envs/.env.sample: -------------------------------------------------------------------------------- 1 | SECRET_KEY = secret_key 2 | DEBUG = debug_status 3 | 4 | #admin panel url 5 | ADMIN_URL = ADMIN_PANNEL_URL 6 | 7 | # silk panel url (for query optimazation checking) 8 | SILK_URL = silk-panel 9 | 10 | # SMS PANNEL Config 11 | MELIPAYAMAK_USERNAME=sms_panel_username 12 | MELIPAYAMAK_PASSWORD=sms_panel_password 13 | 14 | DB_ENGINE = django.db.backends.postgresql 15 | DB_NAME = postgres 16 | DB_USER = postgres 17 | DB_PASS = postgres 18 | DB_HOST = db 19 | DB_PORT = 5432 -------------------------------------------------------------------------------- /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", "core.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 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # main packages 2 | django >3.2<3.3 3 | python-decouple 4 | Pillow 5 | djangorestframework 6 | 7 | # env controlls 8 | python-decouple 9 | 10 | #test 11 | pytest 12 | pytest-django 13 | 14 | #Third party modules 15 | markdown 16 | django-filter 17 | 18 | # third party packages 19 | djangorestframework_simplejwt 20 | markdown 21 | django-filter 22 | django-decouple 23 | django-cors-headers 24 | 25 | 26 | # linting and test packages 27 | flake8 28 | black 29 | 30 | # # api docs 31 | # drf-yasg 32 | # drf-yasg[validation] 33 | 34 | #drf-yasg[validation] 35 | drf-spectacular 36 | djangorestframework-simplejwt 37 | 38 | # sms pannel 39 | zeep 40 | requests 41 | aiohttp 42 | asyncio 43 | 44 | # query testing 45 | django-silk -------------------------------------------------------------------------------- /timetable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrzhd/TimeTable/66877939a28dd2d5c752768704e582726958a423/timetable/__init__.py -------------------------------------------------------------------------------- /timetable/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import ( 3 | Section, 4 | Class, 5 | SectionTeacher, 6 | FreeSectionTeacher 7 | ) 8 | 9 | 10 | class SectionAdminConfig(admin.ModelAdmin): 11 | model = Section 12 | search_fields = ["iranian_time", "day", "chinese_time", "month","year", ] 13 | list_filter = ("iranian_time", "day", "chinese_time", "month","year", ) 14 | list_display = ("iranian_time", "day", "chinese_time", "month", "year", ) 15 | add_fieldsets = ("iranian_time", "day", "chinese_time", "month","year", ) 16 | 17 | admin.site.register(Section, SectionAdminConfig) 18 | 19 | class ClassAdminConfig(admin.ModelAdmin): 20 | model = Class 21 | search_fields = ["student_name", "session", "platform",] 22 | list_filter = ("student_name", "session", "platform",) 23 | list_display = ("student_name", "session", "platform",) 24 | add_fieldsets = ("student_name", "session","platform",) 25 | 26 | admin.site.register(Class, ClassAdminConfig) 27 | 28 | class SectionTeacherAdminConfig(admin.ModelAdmin): 29 | model = SectionTeacher 30 | search_fields = ["teacher", "section", "section_class", ] 31 | list_filter = ("teacher", "section", "section_class", ) 32 | list_display = ("teacher", "section", "section_class", ) 33 | add_fieldsets = ("teacher", "section", "section_class", ) 34 | 35 | admin.site.register(SectionTeacher, SectionTeacherAdminConfig) 36 | 37 | 38 | class FreeSectionTeacherAdminConfig(admin.ModelAdmin): 39 | model = FreeSectionTeacher 40 | search_fields = ["teacher", "section", "section_class", ] 41 | list_filter = ("teacher", "section", "section_class", ) 42 | list_display = ("teacher", "section", "section_class", ) 43 | add_fieldsets = ("teacher", "section", "section_class", ) 44 | 45 | admin.site.register(FreeSectionTeacher, FreeSectionTeacherAdminConfig) 46 | 47 | -------------------------------------------------------------------------------- /timetable/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrzhd/TimeTable/66877939a28dd2d5c752768704e582726958a423/timetable/api/__init__.py -------------------------------------------------------------------------------- /timetable/api/serializers.py: -------------------------------------------------------------------------------- 1 | 2 | from rest_framework import serializers 3 | from ..models import Section, SectionTeacher, FreeSectionTeacher, Class 4 | from accounts.models import User 5 | 6 | class TeacherSetFreeSectionSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = FreeSectionTeacher 9 | fields =[ 10 | "section", 11 | ] 12 | 13 | def create(self, validated_data): 14 | validated_data["teacher"] = self.context.get("request").user 15 | return super().create(validated_data) 16 | 17 | 18 | class SupervisorSetFreeSectionSerializer(serializers.ModelSerializer): 19 | class Meta: 20 | model = FreeSectionTeacher 21 | fields =[ 22 | "section", 23 | "teacher", 24 | ] 25 | 26 | 27 | class TeacherSectionAdjustSerializer(serializers.ModelSerializer): 28 | class Meta: 29 | model = SectionTeacher 30 | fields =[ 31 | "teacher", 32 | "section", 33 | ] 34 | 35 | 36 | class SectionListSerializer(serializers.ModelSerializer): 37 | section__chinese_time = serializers.SerializerMethodField() 38 | section__iranian_time = serializers.SerializerMethodField() 39 | section__day = serializers.SerializerMethodField() 40 | section__month = serializers.SerializerMethodField() 41 | section__year = serializers.SerializerMethodField() 42 | 43 | class Meta: 44 | model = SectionTeacher 45 | fields = [ 46 | "section__chinese_time", 47 | "section__iranian_time", 48 | "section__day", 49 | "section__month", 50 | "section__year", 51 | "section_class", 52 | ] 53 | 54 | def get_section__chinese_time(self, obj): 55 | return obj.section.chinese_time 56 | 57 | def get_section__iranian_time(self, obj): 58 | return obj.section.iranian_time 59 | 60 | def get_section__day(self, obj): 61 | return obj.section.day 62 | 63 | def get_section__month(self, obj): 64 | return obj.section.month 65 | 66 | def get_section__year(self, obj): 67 | return obj.section.year 68 | 69 | class AddFreeSectionsToSectionSerializer(serializers.ModelSerializer): 70 | class Meta: 71 | model = FreeSectionTeacher 72 | fields = [ 73 | "teacher", 74 | "section", 75 | "section_class", 76 | ] 77 | 78 | class FreeSectionListSerializer(serializers.ModelSerializer): 79 | section__chinese_time = serializers.SerializerMethodField() 80 | section__iranian_time = serializers.SerializerMethodField() 81 | section__day = serializers.SerializerMethodField() 82 | section__month = serializers.SerializerMethodField() 83 | section__year = serializers.SerializerMethodField() 84 | 85 | class Meta: 86 | model = FreeSectionTeacher 87 | fields = [ 88 | "section__chinese_time", 89 | "section__iranian_time", 90 | "section__day", 91 | "section__month", 92 | "section__year", 93 | "section_class", 94 | ] 95 | 96 | def get_section__chinese_time(self, obj): 97 | return obj.section.chinese_time 98 | 99 | def get_section__iranian_time(self, obj): 100 | return obj.section.iranian_time 101 | 102 | def get_section__day(self, obj): 103 | return obj.section.day 104 | 105 | def get_section__month(self, obj): 106 | return obj.section.month 107 | 108 | def get_section__year(self, obj): 109 | return obj.section.year 110 | 111 | 112 | class TeacherListSectionSerializer(serializers.ModelSerializer): 113 | teacher_email = serializers.CharField(source='teacher.email') 114 | class Meta: 115 | model = SectionTeacher 116 | fields = [ 117 | "teacher_email", 118 | ] 119 | 120 | class TeacherListFreeSectionSerializer(serializers.ModelSerializer): 121 | teacher_email = serializers.CharField(source='teacher.email') 122 | class Meta: 123 | model = FreeSectionTeacher 124 | fields = [ 125 | "teacher_email", 126 | ] 127 | 128 | class SetClassSerializer(serializers.ModelSerializer): 129 | class Meta: 130 | model = FreeSectionTeacher 131 | fields =[ 132 | "teacher", 133 | "section", 134 | "section_class", 135 | ] 136 | -------------------------------------------------------------------------------- /timetable/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ( 3 | CreateSectionsAPIView, 4 | TeacherSetFreeSectionAPIView, 5 | SectionListAPIView, 6 | FreeSectionListAPIView, 7 | TeacherListSectionListAPIView, 8 | TeacherListFreeSectionListAPIView, 9 | SetClassUpdateAPIView, 10 | AddFreeSectionsToSectionsCreateAPIView, 11 | SupervisorSetFreeSectionAPIView, 12 | TeacherFreeSectionListAPIView, 13 | ) 14 | 15 | urlpatterns = [ 16 | 17 | #Teacher Set Free section 18 | path("teacher-set-free-section/", TeacherSetFreeSectionAPIView.as_view(), name="teacher-set-free-section"), 19 | path("supervisor-set-free-section/", SupervisorSetFreeSectionAPIView.as_view(), name="supervisor-set-free-section"), 20 | 21 | #Section Lists: 22 | path("section-list//", SectionListAPIView.as_view(), name="section-list"), 23 | path("free-section-list//", FreeSectionListAPIView.as_view(), name="free-section-list"), 24 | path("teacher-free-section-list/", TeacherFreeSectionListAPIView.as_view(), name="teacher-free-section-list"), 25 | #Teacher lists based on a section 26 | path("teacher-list-section///", TeacherListSectionListAPIView.as_view(), name="teacher-list-section"), 27 | path("teacher-list-free-section///", 28 | TeacherListFreeSectionListAPIView.as_view(), name="teacher-list-free-section"), 29 | 30 | #Class: 31 | path("set-class/", SetClassUpdateAPIView.as_view(), name="set-class"), 32 | 33 | #Adding Free Sections to Section: 34 | path("add-free-section-to-section/", AddFreeSectionsToSectionsCreateAPIView.as_view(), name = "add-free-section-to-section"), 35 | 36 | #Section Creators: 37 | path("section-creator/", CreateSectionsAPIView.as_view(), name="section-creator"), 38 | ] -------------------------------------------------------------------------------- /timetable/api/views.py: -------------------------------------------------------------------------------- 1 | from .serializers import ( 2 | TeacherSetFreeSectionSerializer, 3 | FreeSectionListSerializer, 4 | SectionListSerializer, 5 | TeacherListSectionSerializer, 6 | TeacherListFreeSectionSerializer, 7 | SetClassSerializer, 8 | AddFreeSectionsToSectionSerializer, 9 | SupervisorSetFreeSectionSerializer, 10 | ) 11 | from rest_framework import generics 12 | from rest_framework.views import APIView 13 | from rest_framework.response import Response 14 | from rest_framework import status 15 | from rest_framework.permissions import IsAuthenticated 16 | from rest_framework import generics, status 17 | from rest_framework.response import Response 18 | from rest_framework.permissions import IsAuthenticated 19 | 20 | from timetable.models import Section, SectionTeacher, FreeSectionTeacher 21 | from django_filters.rest_framework import DjangoFilterBackend 22 | from ..filters import SectionFilterBackend 23 | from django.db import IntegrityError 24 | from ..models import Section, iranian_time_slots, chinese_time_slots, DAYS_OF_WEEK, MONTHS_OF_YEAR 25 | from core.permissions import IsConsultant, IsSupervisor 26 | 27 | #---------------------------------Views for Teachers ---------------------------------# 28 | 29 | class TeacherSetFreeSectionAPIView(generics.CreateAPIView): 30 | """ 31 | Teacher sets the free sections 32 | """ 33 | permission_classes = [IsAuthenticated] 34 | serializer_class = TeacherSetFreeSectionSerializer 35 | 36 | def post(self, request): 37 | serializer = self.serializer_class( 38 | data=request.data, many=False, context={"request": self.request} 39 | ) 40 | if serializer.is_valid(): 41 | try: 42 | serializer.save() # Creates the FreeSectionTeacher instance 43 | return Response({'message': 'Free section successfully added'}) 44 | except IntegrityError as e: 45 | return Response( 46 | {'error': f'Teacher has been added before {e}'}, 47 | status=status.HTTP_406_NOT_ACCEPTABLE, 48 | ) 49 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 50 | 51 | class TeacherFreeSectionListAPIView(generics.ListAPIView): 52 | """ 53 | Gives a list of free sections of a specific teacher (based on teacher id) 54 | """ 55 | permission_classes = [IsSupervisor, IsAuthenticated] 56 | serializer_class = FreeSectionListSerializer 57 | filter_backends = [DjangoFilterBackend] 58 | def get_queryset(self): 59 | query = FreeSectionTeacher.objects.all().select_related('section') 60 | return query 61 | 62 | def get(self, request, *args, **kwargs): 63 | query = self.get_queryset().filter(teacher = request.user) 64 | serializer = self.serializer_class(query, many=True) 65 | return Response(serializer.data) 66 | 67 | class SupervisorSetFreeSectionAPIView(generics.CreateAPIView): 68 | """ 69 | Teacher sets the free sections 70 | """ 71 | permission_classes = [IsSupervisor, IsAuthenticated] 72 | serializer_class = SupervisorSetFreeSectionSerializer 73 | 74 | def post(self,request): 75 | serializer = self.serializer_class(data=request.data, many=False) 76 | if serializer.is_valid(): 77 | try: 78 | serializer.save() 79 | return Response({'message':'Free section successfully added'}) 80 | except IntegrityError as e: 81 | return Response({'error': f'Teacher has been added before {e}'}, 82 | status=status.HTTP_406_NOT_ACCEPTABLE,) 83 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 84 | 85 | class FreeSectionListAPIView(generics.ListAPIView): 86 | """ 87 | Gives a list of free sections of a specific teacher (based on teacher id) 88 | """ 89 | permission_classes = [IsSupervisor, IsAuthenticated] 90 | serializer_class = FreeSectionListSerializer 91 | filter_backends = [DjangoFilterBackend, SectionFilterBackend] 92 | filterset_fields = { 93 | "section__day": ['exact'], 94 | "section__iranian_time": ['exact'], 95 | "section__chinese_time": ['exact'], 96 | "section__month": ['exact'], 97 | "section__year": ['exact'], 98 | } 99 | def get_queryset(self): 100 | personal_id = self.kwargs['personal_id'].upper() 101 | query = FreeSectionTeacher.objects.filter(teacher__personal_id=personal_id).select_related('section') 102 | return query 103 | 104 | def get(self, request, *args, **kwargs): 105 | query = self.get_queryset() 106 | filtered_query = self.filter_queryset(query) 107 | serializer = self.serializer_class(filtered_query, many=True) 108 | return Response(serializer.data) 109 | 110 | 111 | class SectionListAPIView(generics.ListAPIView): 112 | """ 113 | Gives a list of sections of a specific teacher (based on teacher id) 114 | """ 115 | permission_classes = [IsSupervisor, IsAuthenticated] 116 | serializer_class = SectionListSerializer 117 | filter_backends = [DjangoFilterBackend, SectionFilterBackend] 118 | filterset_fields = { 119 | "section__day": ['exact'], 120 | "section__iranian_time": ['exact'], 121 | "section__chinese_time": ['exact'], 122 | "section__month": ['exact'], 123 | "section__year": ['exact'], 124 | } 125 | def get_queryset(self): 126 | personal_id = self.kwargs['personal_id'] 127 | query = SectionTeacher.objects.filter(teacher__personal_id=personal_id).select_related('section') 128 | return query 129 | 130 | def get(self, request, *args, **kwargs): 131 | query = self.get_queryset() 132 | filtered_query = self.filter_queryset(query) 133 | serializer = self.serializer_class(filtered_query, many=True) 134 | return Response(serializer.data) 135 | 136 | class TeacherListSectionListAPIView(generics.ListAPIView): 137 | """ 138 | Gives a list of teachers of a given section 139 | """ 140 | permission_classes = [IsSupervisor, IsAuthenticated] 141 | serializer_class = TeacherListSectionSerializer 142 | filter_backends = [DjangoFilterBackend] 143 | def get_queryset(self): 144 | day = self.kwargs['day'] 145 | iranian_time = self.kwargs['iranian_time'] 146 | query = SectionTeacher.objects.filter(section__day=day, 147 | section__iranian_time=iranian_time ).select_related('teacher') 148 | return query 149 | 150 | def get(self, request, *args, **kwargs): 151 | queryset = self.get_queryset() 152 | serializer = self.serializer_class(queryset, many=True) 153 | return Response(serializer.data) 154 | 155 | class TeacherListFreeSectionListAPIView(generics.ListAPIView): 156 | """ 157 | Gives a list of teachers of a given free section 158 | """ 159 | permission_classes = [IsSupervisor, IsAuthenticated] 160 | serializer_class = TeacherListFreeSectionSerializer 161 | filter_backends = [DjangoFilterBackend] 162 | # filterset_fields = [] 163 | 164 | def get_queryset(self): 165 | day = self.kwargs['day'] 166 | iranian_time = self.kwargs['iranian_time'] 167 | query = FreeSectionTeacher.objects.filter(section__day=day, 168 | section__iranian_time=iranian_time).select_related('teacher') 169 | return query 170 | 171 | def get(self, request, *args, **kwargs): 172 | query = self.get_queryset() 173 | serializer = self.serializer_class(query, many=True) 174 | return Response(serializer.data) 175 | 176 | class SetClassUpdateAPIView(generics.UpdateAPIView): 177 | """ 178 | Set an a Student to a teacher in a free section 179 | """ 180 | permission_classes = [IsSupervisor, IsAuthenticated] 181 | serializer_class = SetClassSerializer 182 | queryset = FreeSectionTeacher.objects.all() 183 | 184 | def update(self, request, *args, **kwargs): 185 | serializer = self.get_serializer(data=request.data) 186 | serializer.is_valid(raise_exception=True) 187 | 188 | teacher = serializer.validated_data.get('teacher') 189 | section = serializer.validated_data.get('section') 190 | section_class = serializer.validated_data.get('section_class') 191 | 192 | try: 193 | free_section_teacher = self.get_queryset().get(teacher=teacher, section=section) 194 | except FreeSectionTeacher.DoesNotExist: 195 | return Response({'error': 'FreeSectionTeacher not found.'}, status=status.HTTP_404_NOT_FOUND) 196 | free_section_teacher.section_class = section_class 197 | free_section_teacher.save() 198 | return Response({'message': f'Class "{section_class}" has been set .'}, status=status.HTTP_200_OK) 199 | 200 | 201 | class AddFreeSectionsToSectionsCreateAPIView(generics.CreateAPIView): 202 | permission_classes = [IsSupervisor, IsAuthenticated] 203 | serializer_class = AddFreeSectionsToSectionSerializer 204 | 205 | def create(self, request, *args, **kwargs): 206 | serializer = self.get_serializer(data=request.data) 207 | serializer.is_valid(raise_exception=True) 208 | teacher = serializer.validated_data.get('teacher') 209 | free_section = serializer.validated_data.get('free_section') 210 | free_section_class = serializer.validated_data.get('free_section_class') 211 | 212 | try: 213 | free_section_teacher = FreeSectionTeacher.objects.get( 214 | teacher=teacher, free_section=free_section, free_section_class = free_section_class 215 | ) 216 | except FreeSectionTeacher.DoesNotExist: 217 | return Response({'error': 'FreeSectionTeacher not found.'}, status=status.HTTP_404_NOT_FOUND) 218 | 219 | teacher = free_section_teacher.teacher 220 | section = free_section_teacher.free_section 221 | section_class = free_section_teacher.free_section_class 222 | SectionTeacher.objects.create(teacher=teacher,section=section, section_class = section_class) 223 | 224 | return Response({'message': 'Section has been set.'}, status=status.HTTP_200_OK) 225 | 226 | 227 | class CreateSectionsAPIView(APIView): 228 | """ 229 | Creates all the of possible sections 230 | """ 231 | def get(self, request, format=None): 232 | try: 233 | Section.objects.all().delete() 234 | iranian_time_slots = [choice[0] for choice in Section.iranian_time.field.choices] 235 | chinese_time_slots = [choice[0] for choice in Section.chinese_time.field.choices] 236 | for month,_ in MONTHS_OF_YEAR: 237 | for day, _ in DAYS_OF_WEEK: 238 | for i in range(len(iranian_time_slots)): 239 | iranian_time = iranian_time_slots[i] 240 | chinese_time = chinese_time_slots[i] 241 | Section.objects.create(month=month, day=day, iranian_time=iranian_time, chinese_time=chinese_time) 242 | return Response({'message': 'All sections created successfully'}, status=status.HTTP_201_CREATED) 243 | except Exception as e: 244 | return Response({'error': f'{e}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) 245 | 246 | -------------------------------------------------------------------------------- /timetable/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TimetableConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'timetable' 7 | -------------------------------------------------------------------------------- /timetable/filters.py: -------------------------------------------------------------------------------- 1 | from rest_framework import filters 2 | from django.db.models import Q 3 | from functools import reduce 4 | from operator import or_ 5 | 6 | class SectionFilterBackend(filters.BaseFilterBackend): 7 | """ 8 | Filtering a time section based on its fields 9 | """ 10 | def filter_queryset(self, request, queryset, view): 11 | filters = [] 12 | for param, value in view.request.query_params.items(): 13 | if param.startswith("section__"): 14 | query_filter = Q(**{param: value}) 15 | filters.append(query_filter) 16 | if filters: 17 | combined_filter = reduce(or_, filters) 18 | queryset = queryset.filter(combined_filter) 19 | 20 | return queryset -------------------------------------------------------------------------------- /timetable/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-23 06:38 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | initial = True 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Class", 18 | fields=[ 19 | ( 20 | "id", 21 | models.BigAutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("student_english_name", models.CharField(max_length=50)), 29 | ( 30 | "student_chinese_name", 31 | models.CharField(blank=True, max_length=50, null=True), 32 | ), 33 | ("session", models.PositiveBigIntegerField()), 34 | ("platform", models.CharField(blank=True, max_length=50, null=True)), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name="FreeSection", 39 | fields=[ 40 | ("section_id", models.AutoField(primary_key=True, serialize=False)), 41 | ( 42 | "iranian_time", 43 | models.CharField( 44 | choices=[ 45 | ("9:00 - 9:45", "9:00 - 9:45"), 46 | ("10:00 - 10:45", "10:00 - 10:45"), 47 | ("11:00 - 11:45", "11:00 - 11:45"), 48 | ("12:00 - 12:45", "12:00 - 12:45"), 49 | ("13:00 - 13:45", "13:00 - 13:45"), 50 | ("14:00 - 14:45", "14:00 - 14:45"), 51 | ("15:00 - 15:45", "15:00 - 15:45"), 52 | ("16:00 - 16:45", "16:00 - 16:45"), 53 | ("17:00 - 17:45", "17:00 - 17:45"), 54 | ("18:00 - 18:45", "18:00 - 18:45"), 55 | ("19:00 - 19:45", "19:00 - 19:45"), 56 | ("20:00 - 20:45", "20:00 - 20:45"), 57 | ("21:00 - 21:45", "21:00 - 21:45"), 58 | ], 59 | default="9:00 - 9:45", 60 | max_length=50, 61 | ), 62 | ), 63 | ( 64 | "chinese_time", 65 | models.CharField( 66 | choices=[ 67 | ("4:30 - 5:15", "4:30 - 5:15"), 68 | ("5:30 - 6:15", "5:30 - 6:15"), 69 | ("6:30 - 7:15", "6:30 - 7:15"), 70 | ("7:30 - 8:15", "7:30 - 8:15"), 71 | ("8:30 - 9:15", "8:30 - 9:15"), 72 | ("9:30 - 10:15", "9:30 - 10:15"), 73 | ("10:30 - 11:15", "10:30 - 11:15"), 74 | ("11:30 - 12:15", "11:30 - 12:15"), 75 | ("12:30 - 13:15", "12:30 - 13:15"), 76 | ("13:30 - 14:15", "13:30 - 14:15"), 77 | ("14:30 - 15:15", "14:30 - 15:15"), 78 | ("15:30 - 16:15", "15:30 - 16:15"), 79 | ("16:30 - 17:15", "16:30 - 17:15"), 80 | ], 81 | default="4:30 - 5:15", 82 | max_length=50, 83 | ), 84 | ), 85 | ( 86 | "day", 87 | models.CharField( 88 | choices=[ 89 | ("Sunday", "Sunday"), 90 | ("Monday", "Monday"), 91 | ("Tuesday", "Tuesday"), 92 | ("Wednesday", "Wednesday"), 93 | ("Thursday", "Thursday"), 94 | ("Friday", "Friday"), 95 | ("Saturday", "Saturday"), 96 | ], 97 | default="Sunday", 98 | max_length=15, 99 | ), 100 | ), 101 | ], 102 | ), 103 | migrations.CreateModel( 104 | name="Section", 105 | fields=[ 106 | ("section_id", models.AutoField(primary_key=True, serialize=False)), 107 | ( 108 | "iranian_time", 109 | models.CharField( 110 | choices=[ 111 | ("9:00 - 9:45", "9:00 - 9:45"), 112 | ("10:00 - 10:45", "10:00 - 10:45"), 113 | ("11:00 - 11:45", "11:00 - 11:45"), 114 | ("12:00 - 12:45", "12:00 - 12:45"), 115 | ("13:00 - 13:45", "13:00 - 13:45"), 116 | ("14:00 - 14:45", "14:00 - 14:45"), 117 | ("15:00 - 15:45", "15:00 - 15:45"), 118 | ("16:00 - 16:45", "16:00 - 16:45"), 119 | ("17:00 - 17:45", "17:00 - 17:45"), 120 | ("18:00 - 18:45", "18:00 - 18:45"), 121 | ("19:00 - 19:45", "19:00 - 19:45"), 122 | ("20:00 - 20:45", "20:00 - 20:45"), 123 | ("21:00 - 21:45", "21:00 - 21:45"), 124 | ], 125 | default="9:00 - 9:45", 126 | max_length=50, 127 | ), 128 | ), 129 | ( 130 | "chinese_time", 131 | models.CharField( 132 | choices=[ 133 | ("4:30 - 5:15", "4:30 - 5:15"), 134 | ("5:30 - 6:15", "5:30 - 6:15"), 135 | ("6:30 - 7:15", "6:30 - 7:15"), 136 | ("7:30 - 8:15", "7:30 - 8:15"), 137 | ("8:30 - 9:15", "8:30 - 9:15"), 138 | ("9:30 - 10:15", "9:30 - 10:15"), 139 | ("10:30 - 11:15", "10:30 - 11:15"), 140 | ("11:30 - 12:15", "11:30 - 12:15"), 141 | ("12:30 - 13:15", "12:30 - 13:15"), 142 | ("13:30 - 14:15", "13:30 - 14:15"), 143 | ("14:30 - 15:15", "14:30 - 15:15"), 144 | ("15:30 - 16:15", "15:30 - 16:15"), 145 | ("16:30 - 17:15", "16:30 - 17:15"), 146 | ], 147 | default="4:30 - 5:15", 148 | max_length=50, 149 | ), 150 | ), 151 | ( 152 | "day", 153 | models.CharField( 154 | choices=[ 155 | ("Sunday", "Sunday"), 156 | ("Monday", "Monday"), 157 | ("Tuesday", "Tuesday"), 158 | ("Wednesday", "Wednesday"), 159 | ("Thursday", "Thursday"), 160 | ("Friday", "Friday"), 161 | ("Saturday", "Saturday"), 162 | ], 163 | default="Sunday", 164 | max_length=15, 165 | ), 166 | ), 167 | ], 168 | ), 169 | migrations.CreateModel( 170 | name="SectionTeacher", 171 | fields=[ 172 | ( 173 | "id", 174 | models.BigAutoField( 175 | auto_created=True, 176 | primary_key=True, 177 | serialize=False, 178 | verbose_name="ID", 179 | ), 180 | ), 181 | ( 182 | "section", 183 | models.ForeignKey( 184 | null=True, 185 | on_delete=django.db.models.deletion.CASCADE, 186 | to="timetable.section", 187 | ), 188 | ), 189 | ( 190 | "section_class", 191 | models.ManyToManyField(blank=True, to="timetable.class"), 192 | ), 193 | ( 194 | "teacher", 195 | models.ForeignKey( 196 | on_delete=django.db.models.deletion.CASCADE, 197 | to=settings.AUTH_USER_MODEL, 198 | ), 199 | ), 200 | ], 201 | ), 202 | migrations.CreateModel( 203 | name="FreeSectionTeacher", 204 | fields=[ 205 | ( 206 | "id", 207 | models.BigAutoField( 208 | auto_created=True, 209 | primary_key=True, 210 | serialize=False, 211 | verbose_name="ID", 212 | ), 213 | ), 214 | ( 215 | "free_section", 216 | models.ForeignKey( 217 | blank=True, 218 | null=True, 219 | on_delete=django.db.models.deletion.CASCADE, 220 | to="timetable.freesection", 221 | ), 222 | ), 223 | ( 224 | "free_section_class", 225 | models.ForeignKey( 226 | blank=True, 227 | null=True, 228 | on_delete=django.db.models.deletion.CASCADE, 229 | to="timetable.class", 230 | ), 231 | ), 232 | ( 233 | "teacher", 234 | models.ForeignKey( 235 | on_delete=django.db.models.deletion.CASCADE, 236 | to=settings.AUTH_USER_MODEL, 237 | ), 238 | ), 239 | ], 240 | ), 241 | ] 242 | -------------------------------------------------------------------------------- /timetable/migrations/0002_alter_freesectionteacher_free_section.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-23 12:18 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("timetable", "0001_initial"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="freesectionteacher", 15 | name="free_section", 16 | field=models.ForeignKey( 17 | blank=True, 18 | null=True, 19 | on_delete=django.db.models.deletion.CASCADE, 20 | to="timetable.section", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /timetable/migrations/0003_alter_freesectionteacher_free_section.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-23 12:20 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("timetable", "0002_alter_freesectionteacher_free_section"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="freesectionteacher", 15 | name="free_section", 16 | field=models.ForeignKey( 17 | blank=True, 18 | null=True, 19 | on_delete=django.db.models.deletion.CASCADE, 20 | to="timetable.freesection", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /timetable/migrations/0004_alter_freesectionteacher_free_section.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-23 12:21 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("timetable", "0003_alter_freesectionteacher_free_section"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="freesectionteacher", 15 | name="free_section", 16 | field=models.ForeignKey( 17 | blank=True, 18 | null=True, 19 | on_delete=django.db.models.deletion.CASCADE, 20 | to="timetable.section", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /timetable/migrations/0005_remove_freesectionteacher_free_section_class_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-24 09:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | dependencies = [ 8 | ("timetable", "0004_alter_freesectionteacher_free_section"), 9 | ] 10 | 11 | operations = [ 12 | migrations.RemoveField( 13 | model_name="freesectionteacher", 14 | name="free_section_class", 15 | ), 16 | migrations.AddField( 17 | model_name="freesectionteacher", 18 | name="free_section_class", 19 | field=models.ManyToManyField(blank=True, to="timetable.class"), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /timetable/migrations/0006_remove_freesectionteacher_free_section_class_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.3 on 2023-07-24 10:03 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | dependencies = [ 9 | ("timetable", "0005_remove_freesectionteacher_free_section_class_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="freesectionteacher", 15 | name="free_section_class", 16 | ), 17 | migrations.RemoveField( 18 | model_name="sectionteacher", 19 | name="section_class", 20 | ), 21 | migrations.AddField( 22 | model_name="freesectionteacher", 23 | name="free_section_class", 24 | field=models.ForeignKey( 25 | blank=True, 26 | null=True, 27 | on_delete=django.db.models.deletion.CASCADE, 28 | to="timetable.class", 29 | ), 30 | ), 31 | migrations.AddField( 32 | model_name="sectionteacher", 33 | name="section_class", 34 | field=models.ForeignKey( 35 | blank=True, 36 | null=True, 37 | on_delete=django.db.models.deletion.CASCADE, 38 | to="timetable.class", 39 | ), 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /timetable/migrations/0007_auto_20230730_1239.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-07-30 09:09 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('timetable', '0006_remove_freesectionteacher_free_section_class_and_more'), 10 | ] 11 | 12 | operations = [ 13 | migrations.DeleteModel( 14 | name='FreeSection', 15 | ), 16 | migrations.RenameField( 17 | model_name='class', 18 | old_name='student_english_name', 19 | new_name='student_name', 20 | ), 21 | migrations.RemoveField( 22 | model_name='class', 23 | name='student_chinese_name', 24 | ), 25 | migrations.AddField( 26 | model_name='section', 27 | name='month', 28 | field=models.CharField(choices=[('January', 'January'), ('February', 'February'), ('March', 'March'), ('April', 'April'), ('May', 'May'), ('June', 'June'), ('July', 'July'), ('August', 'August'), ('September', 'September'), ('October', 'October'), ('November', 'November'), ('December', 'December')], default='January', max_length=15), 29 | ), 30 | migrations.AddField( 31 | model_name='section', 32 | name='year', 33 | field=models.CharField(default='2023', max_length=15), 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /timetable/migrations/0008_auto_20230802_1629.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.18 on 2023-08-02 12:59 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('timetable', '0007_auto_20230730_1239'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='freesectionteacher', 15 | old_name='free_section', 16 | new_name='section', 17 | ), 18 | migrations.RenameField( 19 | model_name='freesectionteacher', 20 | old_name='free_section_class', 21 | new_name='section_class', 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /timetable/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amrzhd/TimeTable/66877939a28dd2d5c752768704e582726958a423/timetable/migrations/__init__.py -------------------------------------------------------------------------------- /timetable/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from accounts.models import User 3 | from django.utils import timezone 4 | 5 | chinese_time_slots = ( 6 | ('4:30 - 5:15', '4:30 - 5:15'), 7 | ('5:30 - 6:15', '5:30 - 6:15'), 8 | ('6:30 - 7:15', '6:30 - 7:15'), 9 | ('7:30 - 8:15', '7:30 - 8:15'), 10 | ('8:30 - 9:15', '8:30 - 9:15'), 11 | ('9:30 - 10:15', '9:30 - 10:15'), 12 | ('10:30 - 11:15', '10:30 - 11:15'), 13 | ('11:30 - 12:15', '11:30 - 12:15'), 14 | ('12:30 - 13:15', '12:30 - 13:15'), 15 | ('13:30 - 14:15', '13:30 - 14:15'), 16 | ('14:30 - 15:15', '14:30 - 15:15'), 17 | ('15:30 - 16:15', '15:30 - 16:15'), 18 | ('16:30 - 17:15', '16:30 - 17:15'), 19 | ) 20 | 21 | iranian_time_slots = ( 22 | ('9:00 - 9:45', '9:00 - 9:45'), 23 | ('10:00 - 10:45', '10:00 - 10:45'), 24 | ('11:00 - 11:45', '11:00 - 11:45'), 25 | ('12:00 - 12:45', '12:00 - 12:45'), 26 | ('13:00 - 13:45', '13:00 - 13:45'), 27 | ('14:00 - 14:45', '14:00 - 14:45'), 28 | ('15:00 - 15:45', '15:00 - 15:45'), 29 | ('16:00 - 16:45', '16:00 - 16:45'), 30 | ('17:00 - 17:45', '17:00 - 17:45'), 31 | ('18:00 - 18:45', '18:00 - 18:45'), 32 | ('19:00 - 19:45', '19:00 - 19:45'), 33 | ('20:00 - 20:45', '20:00 - 20:45'), 34 | ('21:00 - 21:45', '21:00 - 21:45'), 35 | ) 36 | 37 | DAYS_OF_WEEK = ( 38 | ('Sunday', 'Sunday'), 39 | ('Monday', 'Monday'), 40 | ('Tuesday', 'Tuesday'), 41 | ('Wednesday', 'Wednesday'), 42 | ('Thursday', 'Thursday'), 43 | ('Friday', 'Friday'), 44 | ('Saturday', 'Saturday'), 45 | ) 46 | 47 | MONTHS_OF_YEAR = ( 48 | ('January', 'January'), 49 | ('February', 'February'), 50 | ('March', 'March'), 51 | ('April', 'April'), 52 | ('May', 'May'), 53 | ('June', 'June'), 54 | ('July', 'July'), 55 | ('August', 'August'), 56 | ('September', 'September'), 57 | ('October', 'October'), 58 | ('November', 'November'), 59 | ('December', 'December'), 60 | ) 61 | 62 | class Section(models.Model): 63 | section_id = models.AutoField(primary_key=True) 64 | iranian_time = models.CharField(max_length=50, choices=iranian_time_slots, 65 | default='9:00 - 9:45', null=False, blank=False) 66 | chinese_time = models.CharField(max_length=50, choices=chinese_time_slots, 67 | default='4:30 - 5:15', null=False, blank=False) 68 | day = models.CharField(max_length=15, default='Sunday',choices=DAYS_OF_WEEK, null=False, blank=False) 69 | month = models.CharField(max_length=15, default='January',choices=MONTHS_OF_YEAR, null=False, blank=False) 70 | year = models.CharField(max_length=15, default='2023',null=False, blank=False) 71 | 72 | def __str__(self): 73 | return f"{self.chinese_time}/ {self.iranian_time} - {self.day} - {self.month}" 74 | 75 | @property 76 | def name(self): 77 | return f"{self.chinese_time}/ {self.iranian_time} - {self.day} - {self.month}" 78 | 79 | class Class(models.Model): 80 | student_name = models.CharField(null=False, blank=False, max_length=50) 81 | session = models.PositiveBigIntegerField() 82 | platform = models.CharField(null=True, blank=True, max_length=50) 83 | 84 | def __str__(self): 85 | return f"{self.student_name}: session({self.session}) - platform: {self.platform}" 86 | 87 | class SectionTeacher(models.Model): 88 | teacher = models.ForeignKey(User, blank=False, on_delete=models.CASCADE) 89 | section = models.ForeignKey(Section, blank=False, null=True, on_delete=models.CASCADE) 90 | section_class = models.ForeignKey(Class, blank=True, null=True, on_delete=models.CASCADE) 91 | 92 | def __str__(self): 93 | return f"{self.teacher}: {self.section} - {self.section_class}" 94 | 95 | class FreeSectionTeacher(models.Model): 96 | teacher = models.ForeignKey(User, blank=False, on_delete=models.CASCADE) 97 | section = models.ForeignKey(Section, blank=True, null=True, on_delete=models.CASCADE) 98 | section_class = models.ForeignKey(Class, blank=True, null=True, on_delete=models.CASCADE) 99 | 100 | def __str__(self): 101 | return f"{self.teacher}: {self.section} - {self.section_class}" 102 | -------------------------------------------------------------------------------- /timetable/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /timetable/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | 3 | app_name = "timetable" 4 | 5 | urlpatterns = [ 6 | 7 | path("api/", include("timetable.api.urls")) 8 | ] 9 | -------------------------------------------------------------------------------- /timetable/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | --------------------------------------------------------------------------------