├── .dockerignore ├── .github └── workflows │ └── fortify.yml ├── .gitignore ├── .vscode └── settings.json ├── Dockerfile ├── Pipfile ├── Pipfile.lock ├── Procfile ├── README.md ├── accounts ├── __init__.py ├── admin.py ├── apps.py ├── forms.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_alter_baseaccount_email.py │ ├── 0003_alter_baseaccount_contact_no_and_more.py │ ├── 0004_alter_baseaccount_options.py │ └── __init__.py ├── models.py ├── serializers.py ├── tests.py ├── urls.py └── views.py ├── class_diagrams ├── classroomAPI_class_diagram.png ├── img_3.png ├── img_accounts.png └── img_classroom.png ├── classroom ├── __init__.py ├── admin.py ├── apps.py ├── constants.py ├── managers.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_allowedteacher.py │ ├── 0003_teacher.py │ ├── 0004_classroom.py │ ├── 0005_teacher_classroom.py │ ├── 0006_semester.py │ ├── 0007_student.py │ ├── 0008_allowedstudents.py │ ├── 0009_subject.py │ ├── 0010_announcement.py │ ├── 0011_notes.py │ ├── 0012_notesattachmentfile.py │ ├── 0013_alter_college_allowed_teacher_list.py │ ├── 0014_remove_teacher_classroom_classroom_teachers.py │ ├── 0015_alter_teacher_user.py │ ├── 0016_alter_classroom_teachers.py │ ├── 0017_allowedteacherclassroomlevel.py │ ├── 0018_alter_classroom_teachers.py │ ├── 0019_alter_notesattachmentfile_title.py │ ├── 0020_alter_notesattachmentfile_file_path.py │ ├── 0021_assignment.py │ ├── 0022_alter_assignment_options_alter_assignment_due_time.py │ ├── 0023_alter_assignment_options_assignment_alloted_marks_and_more.py │ ├── 0024_delete_assignment.py │ ├── 0025_collegedba.py │ ├── 0026_alter_allowedteacher_email.py │ ├── 0027_allowedcollegedba.py │ ├── 0028_alter_collegedba_college.py │ ├── 0029_alter_college_allowed_teacher_list.py │ ├── 0030_college_allowed_dba_list.py │ ├── 0031_college_owner_email_id_collegedba_is_owner.py │ ├── 0032_alter_collegedba_college.py │ ├── 0033_alter_college_allowed_dba_list.py │ ├── 0034_alter_classroom_slug.py │ ├── 0035_alter_classroom_allowed_student_list_and_more.py │ ├── 0036_college_stream_list_stream.py │ ├── 0037_alter_stream_options_alter_subject_credit_points.py │ ├── 0038_alter_subject_slug.py │ ├── 0039_alter_subject_slug.py │ ├── 0040_assignment.py │ ├── 0041_alter_assignment_due_time_submittedassignment.py │ ├── 0042_alter_assignment_due_date_alter_assignment_due_time.py │ ├── 0043_rename_submittedassignment_assignmentsubmission.py │ ├── 0044_remove_assignment_given_by_and_more.py │ ├── 0045_alter_college_options.py │ ├── 0046_alter_subject_options.py │ ├── 0047_alter_stream_unique_together.py │ ├── 0048_alter_allowedstudents_unique_together_and_more.py │ ├── 0049_alter_classroom_section.py │ └── __init__.py ├── model.py ├── models │ ├── __init__.py │ ├── announcement.py │ ├── assignment.py │ ├── classroom.py │ ├── college.py │ ├── college_dba.py │ ├── imports.py │ ├── notes.py │ ├── student.py │ ├── subject.py │ └── teacher.py ├── routers │ ├── dba_urls.py │ ├── students_urls.py │ └── teacher_urls.py ├── serializers │ ├── __init__.py │ ├── classroom.py │ ├── college_dba.py │ ├── student.py │ ├── teacher.py │ └── usertype.py ├── signals │ ├── __init__.py │ ├── classroom_handlers.py │ ├── college_handlers.py │ ├── common_imports.py │ ├── dba_handlers.py │ ├── handlers.py │ ├── profile_handlers.py │ ├── teacher_classroom_handlers.py │ └── user_handlers.py ├── tasks.py ├── tests.py ├── urls.py ├── validators.py └── views │ ├── __init__.py │ ├── college_dba_view.py │ ├── student_view.py │ ├── teacher_view.py │ └── usertype_view.py ├── core ├── __init__.py ├── asgi.py ├── celery.py ├── mail.py ├── settings │ ├── common.py │ ├── dev.py │ └── prod.py ├── swagger_schema.py ├── urls.py └── wsgi.py ├── docker-compose.yml ├── docker-entrypoint.sh ├── docs └── scripts.md ├── manage.py ├── readme ├── BackendAPI_Doc.jpeg ├── CreateCollegePage.jpeg ├── DjangoADMIN.jpeg ├── Docker.png ├── HomePage.jpeg ├── LogInPage.jpeg ├── Major Project Presentation.pptx ├── Major Project SEM_4 Doc Final Group.pdf ├── SignUpPage.jpeg ├── SubjectAddByStudent.jpeg ├── celery.png ├── djangoIcon.png ├── drf.png ├── python.png ├── redis_icon.png ├── smtp.png └── whole_sw_review.mp4 ├── requirements.txt └── wait-for-it.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | **/migrations/* -------------------------------------------------------------------------------- /.github/workflows/fortify.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | ################################################################################################################################################ 7 | # Fortify lets you build secure software fast with an appsec platform that automates testing throughout the DevSecOps pipeline. Fortify static,# 8 | # dynamic, interactive, and runtime security testing is available on premises or as a service. To learn more about Fortify, start a free trial # 9 | # or contact our sales team, visit microfocus.com/appsecurity. # 10 | # # 11 | # Use this workflow template as a basis for integrating Fortify on Demand Static Application Security Testing(SAST) into your GitHub workflows.# 12 | # This template demonstrates the steps to prepare the code+dependencies, initiate a scan, download results once complete and import into # 13 | # GitHub Security Code Scanning Alerts. Existing customers should review inputs and environment variables below to configure scanning against # 14 | # an existing application in your Fortify on Demand tenant. Additional information is available in the comments throughout the workflow, the # 15 | # documentation for the Fortify actions used, and the Fortify on Demand / ScanCentral Client product documentation. If you need additional # 16 | # assistance with configuration, feel free to create a help ticket in the Fortify on Demand portal. # 17 | ################################################################################################################################################ 18 | 19 | name: Fortify on Demand Scan 20 | 21 | # TODO: Customize trigger events based on your DevSecOps processes and typical FoD SAST scan time 22 | on: 23 | workflow_dispatch: 24 | push: 25 | branches: [ "main" ] 26 | schedule: 27 | - cron: '16 3 * * 0' 28 | 29 | jobs: 30 | FoD-SAST-Scan: 31 | # Use the appropriate runner for building your source code. 32 | # TODO: Use a Windows runner for .NET projects that use msbuild. Additional changes to RUN commands will be required to switch to Windows syntax. 33 | runs-on: ubuntu-latest 34 | permissions: 35 | actions: read 36 | contents: read 37 | security-events: write 38 | 39 | steps: 40 | # Check out source code 41 | - name: Check Out Source Code 42 | uses: actions/checkout@v3 43 | 44 | # Java is required to run the various Fortify utilities. 45 | # When scanning a Java application, please use the appropriate Java version for building your application. 46 | - name: Setup Java 47 | uses: actions/setup-java@v3 48 | with: 49 | java-version: 8 50 | distribution: 'temurin' 51 | 52 | # Prepare source+dependencies for upload. The default example is for a Maven project that uses pom.xml. 53 | # TODO: Update PACKAGE_OPTS based on the ScanCentral Client documentation for your project's included tech stack(s). Helpful hints: 54 | # ScanCentral Client will download dependencies for maven (-bt mvn) and gradle (-bt gradle). 55 | # ScanCentral Client can download dependencies for msbuild projects (-bt msbuild); however, you must convert the workflow to use a Windows runner. 56 | # ScanCentral has additional options that should be set for PHP and Python projects 57 | # For other build tools, add your build commands to download necessary dependencies and prepare according to Fortify on Demand Packaging documentation. 58 | # ScanCentral Client documentation is located at https://www.microfocus.com/documentation/fortify-software-security-center/ 59 | - name: Download Fortify ScanCentral Client 60 | uses: fortify/gha-setup-scancentral-client@5b7382f8234fb9840958c49d5f32ae854115f9f3 61 | - name: Package Code + Dependencies 62 | run: scancentral package $PACKAGE_OPTS -o package.zip 63 | env: 64 | PACKAGE_OPTS: "-bt mvn" 65 | 66 | # Start Fortify on Demand SAST scan and wait until results complete. For more information on FoDUploader commands, see https://github.com/fod-dev/fod-uploader-java 67 | # TODO: Update ENV variables for your application and create the necessary GitHub Secrets. Helpful hints: 68 | # Credentials and release ID should be obtained from your FoD tenant (either Personal Access Token or API Key can be used). 69 | # Automated Audit preference should be configured for the release's Static Scan Settings in the Fortify on Demand portal. 70 | - name: Download Fortify on Demand Universal CI Tool 71 | uses: fortify/gha-setup-fod-uploader@6e6bb8a33cb476e240929fa8ebc739ff110e7433 72 | - name: Perform SAST Scan 73 | run: java -jar $FOD_UPLOAD_JAR -z package.zip -aurl $FOD_API_URL -purl $FOD_URL -rid "$FOD_RELEASE_ID" -tc "$FOD_TENANT" -uc "$FOD_USER" "$FOD_PAT" $FOD_UPLOADER_OPTS -n "$FOD_UPLOADER_NOTES" 74 | env: 75 | FOD_URL: "https://ams.fortify.com/" 76 | FOD_API_URL: "https://api.ams.fortify.com/" 77 | FOD_TENANT: ${{ secrets.FOD_TENANT }} 78 | FOD_USER: ${{ secrets.FOD_USER }} 79 | FOD_PAT: ${{ secrets.FOD_PAT }} 80 | FOD_RELEASE_ID: ${{ secrets.FOD_RELEASE_ID }} 81 | FOD_UPLOADER_OPTS: "-ep 2 -pp 0 -I 1 -apf" 82 | FOD_UPLOADER_NOTES: 'Triggered by GitHub Actions (${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})' 83 | 84 | # Once scan completes, pull SAST issues from Fortify on Demand and generate SARIF output. 85 | - name: Export results to GitHub-optimized SARIF 86 | uses: fortify/gha-export-vulnerabilities@fcb374411cff9809028c911dabb8b57dbdae623b 87 | with: 88 | fod_base_url: "https://ams.fortify.com/" 89 | fod_tenant: ${{ secrets.FOD_TENANT }} 90 | fod_user: ${{ secrets.FOD_USER }} 91 | fod_password: ${{ secrets.FOD_PAT }} 92 | fod_release_id: ${{ secrets.FOD_RELEASE_ID }} 93 | 94 | # Import Fortify on Demand results to GitHub Security Code Scanning 95 | - name: Import Results 96 | uses: github/codeql-action/upload-sarif@v2 97 | with: 98 | sarif_file: ./gh-fortify-sast.sarif 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | /__pycache__ 4 | tokens 5 | /static/ 6 | /media/ 7 | /**/teachers/ 8 | /**/students/ 9 | *.csv 10 | *xlsx 11 | .env 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black" 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.4 2 | 3 | ENV PYTHONUNBUFFERED=1 4 | RUN mkdir /app 5 | WORKDIR /app 6 | 7 | # Required to install mysqlclient with Pip 8 | RUN apt-get update \ 9 | && apt-get install python3-dev default-libmysqlclient-dev gcc -y 10 | # RUN apt-get update \ 11 | # && apt-get install python-dev default-libmysqlclient-dev gcc -y 12 | 13 | # RUN apk update \ 14 | # && apk add --virtual build-deps gcc python3-dev musl-dev \ 15 | # && apk add --no-cache mariadb-dev 16 | 17 | # Install pipenv 18 | RUN pip install --upgrade pip 19 | RUN pip install pipenv 20 | 21 | # Install application dependencies 22 | COPY Pipfile Pipfile.lock /app/ 23 | # We use the --system flag so packages are installed into the system python 24 | # and not into a virtualenv. Docker containers don't need virtual environments. 25 | RUN pipenv install --system --dev --verbose --skip-lock 26 | 27 | # Copy the application files into the image 28 | COPY . /app/ 29 | 30 | # Expose port 8000 on the container 31 | EXPOSE 8000 32 | EXPOSE 3306 33 | 34 | # RUN apk del build-deps -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | django = "*" 8 | djangorestframework = "*" 9 | markdown = "*" 10 | djoser = "*" 11 | djangorestframework-simplejwt = "*" 12 | django-extensions = "*" 13 | drf-yasg = "*" 14 | django-cors-headers = "*" 15 | whitenoise = "*" 16 | gunicorn = "*" 17 | dj-database-url = "*" 18 | psycopg2 = "*" 19 | termcolor = "*" 20 | pandas = "*" 21 | openpyxl = "*" 22 | redis = "*" 23 | celery = "*" 24 | eventlet = "*" 25 | drf-nested-routers = "*" 26 | drf-writable-nested = "*" 27 | 28 | [dev-packages] 29 | black = "*" 30 | django-debug-toolbar = "*" 31 | mysqlclient = "*" 32 | waitress = "*" 33 | 34 | [requires] 35 | python_version = "3.10" 36 | 37 | [pipenv] 38 | allow_prereleases = true 39 | 40 | [scripts] 41 | dev = "docker-compose up -d" 42 | dev-show = "docker-compose up" 43 | logs = 'docker-compose logs -f web' 44 | stop = 'docker-compose down' 45 | db-update = "docker-compose run web python manage.py makemigrations" 46 | db-apply = "docker-compose run web python manage.py migrate" 47 | db-reset = "docker-compose run web python manage.py reset_db" 48 | make-admin = "docker-compose run web python manage.py createsuperuser" 49 | admin-code = 'docker-compose run web python manage.py admin_generator classroom' 50 | cmd = "docker-compose run web python manage.py shell_plus" 51 | lint = "docker-compose run web black ./" 52 | 53 | # dev = "py manage.py runserver 8000" 54 | # serve = "waitress-serve --listen=*:9000 core.wsgi:application" 55 | # db-update = "py manage.py makemigrations" 56 | # db-apply = "py manage.py migrate" 57 | # db_clear = "py manage.py reset_db" 58 | # deploy = "git push heroku main" 59 | # smtp = "docker run -it -p 5000:80 -p 2525:25 rnwood/smtp4dev" 60 | # make-admin = "py manage.py createsuperuser" 61 | # admin-code = 'py manage.py admin_generator classroom' 62 | # redis = "docker run -d -p 6379:6379 redis" 63 | # celery = "celery -A core worker -l info -P eventlet -E" 64 | # cmd = "py manage.py shell_plus" 65 | # lint = "black ./" 66 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: python manage.py migrate 2 | web: gunicorn core.wsgi -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Classroom LMS API (Backend) 2 | 3 | --- 4 | 5 | > #### 4 th Sem Major `Project` 6 | > 7 | > - In this Project I have developed this whole API 8 | > - This API has 5 Modules : 9 | > - Authentication 10 | > - Owner Admin 11 | > - Admin with less privileges 12 | > - Teacher 13 | > - Student 14 | 15 | --- 16 | 17 | > **API Doc** 18 | > 19 | > 20 | 21 | 22 | --- 23 | 24 | ### Contributors 25 | 26 | --- 27 | 28 | > - [Pritam Chakraborty (Backend Dev & Frontend Designer)](https://github.com/PritamChk) 29 | > - [Tathagata Das (Frontend Developer)](https://github.com/TathagataDas99/) 30 | > - [Rimi Mondal (Tester)](https://github.com/RimiDeb13) 31 | 32 | --- 33 | 34 | > #### Project Start Date : 5-Feb-2022 35 | > 36 | > ###### Coding Start Date : 26-April-2022 37 | 38 | --- 39 | 40 | ## Technology Stack 41 | 42 | --- 43 | 44 |

45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |

72 | 73 | > `OS` - `Windows 10` 74 | 75 | --- 76 | 77 | # Recommended Setup 78 | 79 | --- 80 | 81 | > **Install** : 82 | 83 | 1. [VS Code](https://code.visualstudio.com/) 84 | 2.Install [Docker](https://www.docker.com/get-started/) & Run It 85 | 2. Save `docker-entrypoint.sh` & `wait-for-it.sh` with `LF` line feed. 86 | 3. use the following command for first time build: 87 | 88 | ```powershell 89 | docker-compose up --build 90 | ``` 91 | 92 | 5. create super user for django admin area 93 | (one time) 94 | 95 | ```bash 96 | docker-compose run web python manage.py createsuperuser 97 | ``` 98 | 99 | 6. for other commands check out the `Pipfile` 100 | 101 | --- 102 | 103 | ## Some Glimpse of Frontend : 104 | 105 | --- 106 | 107 |

108 | 109 |

110 | 111 |

112 | 113 | 114 |

115 | 116 |

117 | 118 | 119 |

120 | --- 121 | 122 | ## To Know More About Frontend : 123 | > #### [Click Here [↗]](https://github.com/TathagataDas99/Classroom-Frontend) 124 | 125 | --- 126 | -------------------------------------------------------------------------------- /accounts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/accounts/__init__.py -------------------------------------------------------------------------------- /accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib import messages 3 | from django.utils.translation import ngettext 4 | from django.contrib.auth.admin import UserAdmin 5 | 6 | 7 | from .forms import * 8 | from .models import BaseAccount 9 | 10 | admin.site.site_header = "Classroom [LMS]" 11 | 12 | 13 | @admin.register(BaseAccount) 14 | class BaseAccountAdmin(UserAdmin): 15 | form = AccountChangeForm 16 | add_form = AccountCreationForm 17 | add_fieldsets = ( 18 | ("Login Info", {"fields": ("email", "password1", "password2")}), 19 | ( 20 | "Basic Info", 21 | { 22 | "classes": ("wide",), 23 | "fields": ["first_name", "last_name"], 24 | }, 25 | ), 26 | ) 27 | fieldsets = ( 28 | ("Login Info", {"fields": ("id", "email", "password")}), 29 | ("Personal info", {"fields": ("first_name", "last_name", "contact_no")}), 30 | ("Permissions", {"fields": ("is_superuser", "is_staff", "is_active")}), 31 | ( 32 | "Date & Time Info", 33 | { 34 | "fields": ["date_joined", "last_login"], 35 | }, 36 | ), 37 | ( 38 | "Group Permissions", 39 | { 40 | "fields": ( 41 | "groups", 42 | "user_permissions", 43 | ), 44 | }, 45 | ), 46 | ) 47 | readonly_fields = ( 48 | "id", 49 | "date_joined", 50 | ) 51 | ordering = ("email", "first_name", "last_name") 52 | list_display = [ 53 | "email", 54 | "first_name", 55 | "last_name", 56 | "is_active", 57 | "contact_no", 58 | "is_superuser", 59 | "is_staff", 60 | ] 61 | list_display_links = ["email", "first_name"] 62 | list_editable = ["is_active"] 63 | search_fields = ["email", "first_name", "last_name", "contact_no"] 64 | actions = ["make_users_active", "make_users_inactive"] 65 | 66 | @admin.action(description="make user active") 67 | def make_users_active(self, request, queryset): 68 | updated = queryset.update(is_active=True) 69 | self.message_user( 70 | request, 71 | ngettext( 72 | "%d user was successfully activated.", 73 | "%d users were successfully activated.", 74 | updated, 75 | ) 76 | % updated, 77 | messages.SUCCESS, 78 | ) 79 | 80 | @admin.action(description="make user inactive") 81 | def make_users_inactive(self, request, queryset): 82 | updated = queryset.update(is_active=False) 83 | self.message_user( 84 | request, 85 | ngettext( 86 | "%d user was successfully deactivated.", 87 | "%d users were successfully deactivated.", 88 | updated, 89 | ) 90 | % updated, 91 | messages.SUCCESS, 92 | ) 93 | -------------------------------------------------------------------------------- /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/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.forms import UserChangeForm, UserCreationForm 2 | 3 | from .models import BaseAccount 4 | 5 | 6 | class AccountCreationForm(UserCreationForm): 7 | class Meta: 8 | mdoel = BaseAccount 9 | fields = ("email", "first_name", "last_name") 10 | 11 | 12 | class AccountChangeForm(UserChangeForm): 13 | class Meta: 14 | mdoel = BaseAccount 15 | fields = ("email", "first_name", "last_name", "contact_no", "date_joined") 16 | -------------------------------------------------------------------------------- /accounts/managers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import BaseUserManager 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class BaseAccountManager(BaseUserManager): 6 | def create_user( 7 | self, first_name: str, last_name: str, email, password, **extra_fields 8 | ): 9 | """ 10 | Create and save a User with the given email and password. 11 | """ 12 | if not email: 13 | raise ValueError(_("The Email must be set")) 14 | if not first_name: 15 | raise ValueError(_("The First Name must be set")) 16 | if not last_name: 17 | raise ValueError(_("The Last Name must be set")) 18 | if not password: 19 | raise ValueError(_("The Password must be set")) 20 | email = self.normalize_email(email) 21 | user = self.model( 22 | email=email, 23 | first_name=first_name.title(), 24 | last_name=last_name.title(), 25 | **extra_fields 26 | ) 27 | user.set_password(password) 28 | user.save() 29 | return user 30 | 31 | def create_superuser(self, first_name, last_name, email, password, **extra_fields): 32 | """ 33 | Create and save a SuperUser with the given email and password. 34 | """ 35 | extra_fields.setdefault("is_staff", True) 36 | extra_fields.setdefault("is_superuser", True) 37 | extra_fields.setdefault("is_active", True) 38 | 39 | if extra_fields.get("is_staff") is not True: 40 | raise ValueError(_("Superuser must have is_staff=True.")) 41 | if extra_fields.get("is_superuser") is not True: 42 | raise ValueError(_("Superuser must have is_superuser=True.")) 43 | return self.create_user(first_name, last_name, email, password, **extra_fields) 44 | -------------------------------------------------------------------------------- /accounts/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-26 19:56 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | import django.utils.timezone 6 | import uuid 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ("auth", "0012_alter_user_first_name_max_length"), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name="BaseAccount", 20 | fields=[ 21 | ( 22 | "id", 23 | models.UUIDField( 24 | auto_created=True, 25 | default=uuid.uuid4, 26 | editable=False, 27 | primary_key=True, 28 | serialize=False, 29 | ), 30 | ), 31 | ("password", models.CharField(max_length=128, verbose_name="password")), 32 | ( 33 | "last_login", 34 | models.DateTimeField( 35 | blank=True, null=True, verbose_name="last login" 36 | ), 37 | ), 38 | ( 39 | "is_staff", 40 | models.BooleanField( 41 | default=False, 42 | help_text="Designates whether the user can log into this admin site.", 43 | verbose_name="staff status", 44 | ), 45 | ), 46 | ( 47 | "is_active", 48 | models.BooleanField( 49 | default=True, 50 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 51 | verbose_name="active", 52 | ), 53 | ), 54 | ( 55 | "date_joined", 56 | models.DateTimeField( 57 | default=django.utils.timezone.now, verbose_name="date joined" 58 | ), 59 | ), 60 | ("first_name", models.CharField(max_length=200)), 61 | ("last_name", models.CharField(max_length=200)), 62 | ( 63 | "contact_no", 64 | models.CharField( 65 | blank=True, 66 | db_index=True, 67 | help_text="\n 👌🏻E.g - 9881284481\n ❌ +91 9812300122\n ❌ 09812300122\n ", 68 | max_length=15, 69 | null=True, 70 | unique=True, 71 | validators=[ 72 | django.core.validators.RegexValidator( 73 | "^\\d{10}$", "Phone no should contain 10 digits" 74 | ) 75 | ], 76 | verbose_name="Phone No(without country code or 0)", 77 | ), 78 | ), 79 | ( 80 | "email", 81 | models.EmailField( 82 | help_text="This will be used as username for login", 83 | max_length=350, 84 | unique=True, 85 | verbose_name="Email Id", 86 | ), 87 | ), 88 | ( 89 | "is_superuser", 90 | models.BooleanField( 91 | default=False, 92 | help_text="Designates whether the user can edit everything into this admin site.", 93 | verbose_name="admin status", 94 | ), 95 | ), 96 | ( 97 | "groups", 98 | models.ManyToManyField( 99 | blank=True, 100 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 101 | related_name="user_set", 102 | related_query_name="user", 103 | to="auth.group", 104 | verbose_name="groups", 105 | ), 106 | ), 107 | ( 108 | "user_permissions", 109 | models.ManyToManyField( 110 | blank=True, 111 | help_text="Specific permissions for this user.", 112 | related_name="user_set", 113 | related_query_name="user", 114 | to="auth.permission", 115 | verbose_name="user permissions", 116 | ), 117 | ), 118 | ], 119 | options={ 120 | "verbose_name": "User", 121 | "verbose_name_plural": "Users", 122 | "ordering": ["first_name", "last_name"], 123 | }, 124 | ), 125 | ] 126 | -------------------------------------------------------------------------------- /accounts/migrations/0002_alter_baseaccount_email.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-27 03:46 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.AlterField( 14 | model_name="baseaccount", 15 | name="email", 16 | field=models.EmailField( 17 | help_text="This will be used as username for login", 18 | max_length=250, 19 | unique=True, 20 | verbose_name="Email Id", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /accounts/migrations/0003_alter_baseaccount_contact_no_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-27 03:59 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("accounts", "0002_alter_baseaccount_email"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="baseaccount", 16 | name="contact_no", 17 | field=models.CharField( 18 | blank=True, 19 | db_index=True, 20 | help_text="\n 👌🏻E.g - 9881284481\n ❌ +91 9812300122\n ❌ 09812300122\n ", 21 | max_length=15, 22 | null=True, 23 | unique=True, 24 | validators=[ 25 | django.core.validators.RegexValidator( 26 | "^\\d{10}$", "Phone no should contain 10 digits" 27 | ) 28 | ], 29 | verbose_name="Phone No", 30 | ), 31 | ), 32 | migrations.AlterField( 33 | model_name="baseaccount", 34 | name="is_active", 35 | field=models.BooleanField( 36 | default=False, 37 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 38 | verbose_name="active", 39 | ), 40 | ), 41 | ] 42 | -------------------------------------------------------------------------------- /accounts/migrations/0004_alter_baseaccount_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-04-27 06:26 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("accounts", "0003_alter_baseaccount_contact_no_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="baseaccount", 15 | options={ 16 | "ordering": ["first_name", "last_name", "email"], 17 | "verbose_name": "User", 18 | "verbose_name_plural": "Users", 19 | }, 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /accounts/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/accounts/migrations/__init__.py -------------------------------------------------------------------------------- /accounts/models.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid4 2 | 3 | from django.contrib.auth.models import AbstractUser, PermissionsMixin 4 | from django.core.validators import RegexValidator 5 | from django.db import models 6 | from django.utils.translation import gettext_lazy as _ 7 | 8 | from .managers import * 9 | 10 | 11 | class BaseAccount(AbstractUser): 12 | username = None 13 | id = models.UUIDField( 14 | primary_key=True, editable=False, auto_created=True, default=uuid4 15 | ) 16 | first_name = models.CharField(max_length=200) 17 | last_name = models.CharField(max_length=200) 18 | contact_no = models.CharField( 19 | _("Phone No"), 20 | max_length=15, 21 | unique=True, 22 | db_index=True, 23 | blank=True, 24 | null=True, 25 | help_text=""" 26 | 👌🏻E.g - 9881284481 27 | ❌ +91 9812300122 28 | ❌ 09812300122 29 | """, 30 | validators=[RegexValidator(r"^\d{10}$", "Phone no should contain 10 digits")], 31 | ) 32 | email = models.EmailField( 33 | verbose_name="Email Id", 34 | unique=True, 35 | max_length=250, 36 | help_text="This will be used as username for login", 37 | ) 38 | 39 | is_superuser = models.BooleanField( 40 | _("admin status"), 41 | default=False, 42 | help_text=_( 43 | "Designates whether the user can edit everything into this admin site." 44 | ), 45 | ) 46 | is_active = models.BooleanField( 47 | _("active"), 48 | default=False, 49 | help_text=_( 50 | "Designates whether this user should be treated as active. " 51 | "Unselect this instead of deleting accounts." 52 | ), 53 | ) 54 | 55 | objects = BaseAccountManager() 56 | USERNAME_FIELD = "email" 57 | REQUIRED_FIELDS = ["first_name", "last_name"] 58 | 59 | class Meta: 60 | ordering = ["first_name", "last_name", "email"] 61 | verbose_name = "User" 62 | verbose_name_plural = "Users" 63 | -------------------------------------------------------------------------------- /accounts/serializers.py: -------------------------------------------------------------------------------- 1 | from djoser.serializers import ( 2 | UserSerializer as usz, 3 | UsernameResetConfirmSerializer as uname_reset, 4 | ) 5 | 6 | 7 | class UseranmeResetConfirmSerializer(uname_reset): 8 | class Meta(uname_reset.Meta): 9 | fields = ("uid", "token", "email") 10 | 11 | 12 | class CurrentUserSerializer(usz): 13 | """ 14 | description: This will be returned after login authentication 15 | """ 16 | 17 | class Meta(usz.Meta): 18 | fields = ( 19 | "id", 20 | "email", 21 | "first_name", 22 | "last_name", 23 | "contact_no", 24 | "is_active", 25 | "date_joined", 26 | "last_login", 27 | ) 28 | read_only_fields = ("id", "email", "is_active", "date_joined", "last_login") 29 | -------------------------------------------------------------------------------- /accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /accounts/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | CORE URLS 3 | """ 4 | from django.urls import path, include 5 | from .views import * 6 | 7 | urlpatterns = [ 8 | path("", test, name="test-url"), 9 | ] 10 | -------------------------------------------------------------------------------- /accounts/views.py: -------------------------------------------------------------------------------- 1 | from http.client import HTTPResponse 2 | from django.shortcuts import render 3 | from django.http import HttpResponse 4 | 5 | 6 | def test(request): 7 | return HttpResponse("hello") 8 | -------------------------------------------------------------------------------- /class_diagrams/classroomAPI_class_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/class_diagrams/classroomAPI_class_diagram.png -------------------------------------------------------------------------------- /class_diagrams/img_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/class_diagrams/img_3.png -------------------------------------------------------------------------------- /class_diagrams/img_accounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/class_diagrams/img_accounts.png -------------------------------------------------------------------------------- /class_diagrams/img_classroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/class_diagrams/img_classroom.png -------------------------------------------------------------------------------- /classroom/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/classroom/__init__.py -------------------------------------------------------------------------------- /classroom/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ClassroomConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "classroom" 7 | 8 | def ready(self) -> None: 9 | import classroom.signals.handlers 10 | -------------------------------------------------------------------------------- /classroom/constants.py: -------------------------------------------------------------------------------- 1 | from django.db import models as m 2 | 3 | 4 | class LEVEL_CHOICES(m.TextChoices): 5 | UnderGraduate = "Bachelors" 6 | PostGraduate = "Masters" 7 | 8 | 9 | class SECTION_CHOICES(m.TextChoices): 10 | A = "A" 11 | B = "B" 12 | C = "C" 13 | D = "D" 14 | E = "E" 15 | F = "F" 16 | -------------------------------------------------------------------------------- /classroom/managers.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.db.models.signals import post_save 3 | 4 | 5 | class AllowedTeacherClassroomLevelManager(models.Manager): 6 | def delete(self): 7 | return super().delete() 8 | 9 | def bulk_create(self, objs, **kwargs): 10 | a = super(models.Manager, self).bulk_create(objs, **kwargs) 11 | for i in objs: 12 | post_save.send(i.__class__, instance=i, created=True) 13 | return a 14 | -------------------------------------------------------------------------------- /classroom/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 19:34 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | import django_extensions.db.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="College", 17 | fields=[ 18 | ( 19 | "id", 20 | models.BigAutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ( 28 | "slug", 29 | django_extensions.db.fields.AutoSlugField( 30 | blank=True, 31 | editable=False, 32 | populate_from=["name", "state", "city"], 33 | ), 34 | ), 35 | ("name", models.CharField(max_length=255, verbose_name="College Name")), 36 | ("city", models.CharField(max_length=255, verbose_name="City")), 37 | ("state", models.CharField(max_length=255, verbose_name="State")), 38 | ("address", models.TextField(blank=True, null=True)), 39 | ( 40 | "allowed_teacher_list", 41 | models.FileField( 42 | blank=True, 43 | null=True, 44 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/classroom/teachers/", 45 | validators=[ 46 | django.core.validators.FileExtensionValidator( 47 | allowed_extensions=["csv", "xlsx"], 48 | message="Please Upload CSV/XLSX file only", 49 | ) 50 | ], 51 | verbose_name="Upload teacher List File(.csv)", 52 | ), 53 | ), 54 | ], 55 | options={ 56 | "ordering": ["name", "city", "state"], 57 | }, 58 | ), 59 | ] 60 | -------------------------------------------------------------------------------- /classroom/migrations/0002_allowedteacher.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 19:37 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0001_initial"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="AllowedTeacher", 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 | ("email", models.EmailField(max_length=255, verbose_name="Email Id")), 27 | ( 28 | "college", 29 | models.ForeignKey( 30 | on_delete=django.db.models.deletion.CASCADE, 31 | related_name="allowed_teachers", 32 | to="classroom.college", 33 | ), 34 | ), 35 | ], 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /classroom/migrations/0003_teacher.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 19:51 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 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("classroom", "0002_allowedteacher"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Teacher", 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 | ( 29 | "college", 30 | models.ForeignKey( 31 | on_delete=django.db.models.deletion.CASCADE, 32 | related_name="teachers", 33 | to="classroom.college", 34 | ), 35 | ), 36 | ( 37 | "user", 38 | models.OneToOneField( 39 | blank=True, 40 | null=True, 41 | on_delete=django.db.models.deletion.CASCADE, 42 | related_name="teacher_profile", 43 | to=settings.AUTH_USER_MODEL, 44 | ), 45 | ), 46 | ], 47 | options={ 48 | "ordering": ["user__first_name", "user__last_name"], 49 | }, 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /classroom/migrations/0005_teacher_classroom.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 20:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0004_classroom"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="teacher", 15 | name="classroom", 16 | field=models.ManyToManyField( 17 | blank=True, related_name="teachers", to="classroom.classroom" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /classroom/migrations/0006_semester.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 20:31 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("classroom", "0005_teacher_classroom"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Semester", 17 | fields=[ 18 | ( 19 | "id", 20 | models.BigAutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ( 28 | "sem_no", 29 | models.PositiveSmallIntegerField( 30 | validators=[ 31 | django.core.validators.MinValueValidator( 32 | 1, "sem value > 0" 33 | ), 34 | django.core.validators.MaxValueValidator( 35 | 14, "sem value < 15" 36 | ), 37 | ], 38 | verbose_name="Semester No", 39 | ), 40 | ), 41 | ( 42 | "is_current_sem", 43 | models.BooleanField( 44 | default=False, verbose_name="is this sem going on? " 45 | ), 46 | ), 47 | ( 48 | "classroom", 49 | models.ForeignKey( 50 | on_delete=django.db.models.deletion.CASCADE, 51 | related_name="semesters", 52 | to="classroom.classroom", 53 | ), 54 | ), 55 | ], 56 | options={ 57 | "ordering": ["classroom__title", "sem_no"], 58 | "unique_together": {("classroom", "sem_no")}, 59 | }, 60 | ), 61 | ] 62 | -------------------------------------------------------------------------------- /classroom/migrations/0007_student.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 20: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 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("classroom", "0006_semester"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Student", 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 | ( 29 | "university_roll", 30 | models.PositiveBigIntegerField( 31 | blank=True, 32 | help_text="Your University Roll No - (e.g. - 13071020030)", 33 | null=True, 34 | verbose_name="University Roll", 35 | ), 36 | ), 37 | ( 38 | "classroom", 39 | models.ForeignKey( 40 | blank=True, 41 | null=True, 42 | on_delete=django.db.models.deletion.CASCADE, 43 | related_name="students", 44 | to="classroom.classroom", 45 | ), 46 | ), 47 | ( 48 | "user", 49 | models.OneToOneField( 50 | blank=True, 51 | null=True, 52 | on_delete=django.db.models.deletion.CASCADE, 53 | related_name="student_profile", 54 | to=settings.AUTH_USER_MODEL, 55 | ), 56 | ), 57 | ], 58 | options={ 59 | "ordering": ["user__first_name", "user__last_name"], 60 | }, 61 | ), 62 | ] 63 | -------------------------------------------------------------------------------- /classroom/migrations/0008_allowedstudents.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 20:48 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0007_student"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="AllowedStudents", 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 | ("email", models.EmailField(max_length=255, verbose_name="Email Id")), 27 | ( 28 | "university_roll", 29 | models.PositiveBigIntegerField( 30 | help_text="Your University Roll No - (e.g. - 13071020030)", 31 | verbose_name="University Roll", 32 | ), 33 | ), 34 | ( 35 | "classroom", 36 | models.ForeignKey( 37 | on_delete=django.db.models.deletion.CASCADE, 38 | related_name="allowed_students", 39 | to="classroom.classroom", 40 | ), 41 | ), 42 | ], 43 | options={ 44 | "verbose_name_plural": "Allowed Students", 45 | "ordering": ["university_roll"], 46 | "unique_together": { 47 | ("university_roll", "email"), 48 | ("university_roll", "classroom"), 49 | ("classroom", "email"), 50 | }, 51 | }, 52 | ), 53 | ] 54 | -------------------------------------------------------------------------------- /classroom/migrations/0009_subject.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 21:04 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django_extensions.db.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("classroom", "0008_allowedstudents"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Subject", 17 | fields=[ 18 | ( 19 | "id", 20 | models.BigAutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ( 28 | "slug", 29 | django_extensions.db.fields.AutoSlugField( 30 | blank=True, 31 | editable=False, 32 | populate_from=[ 33 | "title", 34 | "semester__sem_no", 35 | "subject_type", 36 | "credit_points", 37 | "created_by", 38 | ], 39 | ), 40 | ), 41 | ("subject_code", models.CharField(max_length=20)), 42 | ("title", models.CharField(max_length=200)), 43 | ( 44 | "subject_type", 45 | models.CharField( 46 | choices=[ 47 | ("TH", "Theory"), 48 | ("PRC", "Practical"), 49 | ("ELC", "Elective"), 50 | ("PRJ", "Project"), 51 | ], 52 | default="TH", 53 | max_length=5, 54 | ), 55 | ), 56 | ( 57 | "credit_points", 58 | models.PositiveSmallIntegerField( 59 | choices=[ 60 | (1, "1"), 61 | (2, "2"), 62 | (3, "3"), 63 | (4, "4"), 64 | (5, "5"), 65 | (6, "6"), 66 | (7, "7"), 67 | (8, "8"), 68 | (9, "9"), 69 | (10, "10"), 70 | (11, "11"), 71 | (12, "12"), 72 | (13, "13"), 73 | (14, "14"), 74 | (15, "15"), 75 | ], 76 | default=1, 77 | ), 78 | ), 79 | ("created_at", models.DateField(auto_now_add=True)), 80 | ( 81 | "created_by", 82 | models.ForeignKey( 83 | on_delete=django.db.models.deletion.CASCADE, 84 | related_name="subjects", 85 | to="classroom.teacher", 86 | ), 87 | ), 88 | ( 89 | "semester", 90 | models.ForeignKey( 91 | on_delete=django.db.models.deletion.CASCADE, 92 | related_name="subjects", 93 | to="classroom.semester", 94 | ), 95 | ), 96 | ], 97 | options={ 98 | "ordering": ["-created_at", "title", "-credit_points"], 99 | }, 100 | ), 101 | ] 102 | -------------------------------------------------------------------------------- /classroom/migrations/0010_announcement.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 21:07 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0009_subject"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="Announcement", 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 | ( 27 | "heading", 28 | models.TextField( 29 | default="No Heading Given", verbose_name="Heading" 30 | ), 31 | ), 32 | ( 33 | "body", 34 | models.TextField( 35 | blank=True, null=True, verbose_name="Description[Optional] " 36 | ), 37 | ), 38 | ( 39 | "created_at", 40 | models.DateTimeField(auto_now_add=True, verbose_name="Created At "), 41 | ), 42 | ( 43 | "updated_at", 44 | models.DateTimeField(auto_now=True, verbose_name="Updated At "), 45 | ), 46 | ( 47 | "posted_by", 48 | models.ForeignKey( 49 | on_delete=django.db.models.deletion.CASCADE, 50 | related_name="announcements", 51 | to="classroom.teacher", 52 | ), 53 | ), 54 | ( 55 | "subject", 56 | models.ForeignKey( 57 | on_delete=django.db.models.deletion.CASCADE, 58 | related_name="announcements", 59 | to="classroom.subject", 60 | ), 61 | ), 62 | ], 63 | options={ 64 | "ordering": ["-updated_at", "-created_at"], 65 | }, 66 | ), 67 | ] 68 | -------------------------------------------------------------------------------- /classroom/migrations/0011_notes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 21:08 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | import django_extensions.db.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("classroom", "0010_announcement"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="Notes", 17 | fields=[ 18 | ( 19 | "id", 20 | models.BigAutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ( 28 | "slug", 29 | django_extensions.db.fields.AutoSlugField( 30 | blank=True, 31 | editable=False, 32 | populate_from=[ 33 | "title", 34 | "subject__title", 35 | "posted_by__user__first_name", 36 | ], 37 | ), 38 | ), 39 | ("title", models.CharField(max_length=255, verbose_name="Title")), 40 | ( 41 | "description", 42 | models.TextField( 43 | blank=True, null=True, verbose_name="Description[optional]" 44 | ), 45 | ), 46 | ( 47 | "created_at", 48 | models.DateTimeField(auto_now_add=True, verbose_name="Created At "), 49 | ), 50 | ( 51 | "updated_at", 52 | models.DateTimeField(auto_now=True, verbose_name="Updated At "), 53 | ), 54 | ( 55 | "posted_by", 56 | models.ForeignKey( 57 | null=True, 58 | on_delete=django.db.models.deletion.SET_NULL, 59 | related_name="created_notes", 60 | to="classroom.teacher", 61 | ), 62 | ), 63 | ( 64 | "subject", 65 | models.ForeignKey( 66 | on_delete=django.db.models.deletion.CASCADE, 67 | related_name="notes", 68 | to="classroom.subject", 69 | ), 70 | ), 71 | ], 72 | options={ 73 | "verbose_name_plural": "Notes", 74 | "ordering": ["title", "-created_at"], 75 | }, 76 | ), 77 | ] 78 | -------------------------------------------------------------------------------- /classroom/migrations/0012_notesattachmentfile.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-09 21:10 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import django_extensions.db.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("classroom", "0011_notes"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="NotesAttachmentFile", 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 | ( 29 | "title", 30 | django_extensions.db.fields.AutoSlugField( 31 | blank=True, 32 | editable=False, 33 | null=True, 34 | populate_from=[ 35 | "notes__title", 36 | "notes__subject__title", 37 | "created_at", 38 | ], 39 | ), 40 | ), 41 | ( 42 | "file_path", 43 | models.FileField( 44 | blank=True, 45 | null=True, 46 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/classroom/notes/%Y/%m/%d", 47 | validators=[ 48 | django.core.validators.FileExtensionValidator( 49 | allowed_extensions=["xlsx", "pdf", "doc"], 50 | message="Please Upload XLSX/PDF/Doc file only", 51 | ) 52 | ], 53 | verbose_name="Upload File Here", 54 | ), 55 | ), 56 | ( 57 | "created_at", 58 | models.DateTimeField(auto_now_add=True, verbose_name="Created At "), 59 | ), 60 | ( 61 | "notes", 62 | models.ForeignKey( 63 | on_delete=django.db.models.deletion.CASCADE, 64 | related_name="attached_files", 65 | to="classroom.notes", 66 | ), 67 | ), 68 | ], 69 | options={ 70 | "ordering": ["id"], 71 | }, 72 | ), 73 | ] 74 | -------------------------------------------------------------------------------- /classroom/migrations/0013_alter_college_allowed_teacher_list.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-10 04:55 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0012_notesattachmentfile"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="college", 16 | name="allowed_teacher_list", 17 | field=models.FileField( 18 | blank=True, 19 | null=True, 20 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/classroom/teachers/", 21 | validators=[ 22 | django.core.validators.FileExtensionValidator( 23 | allowed_extensions=["csv", "xlsx"], 24 | message="Please Upload CSV/XLSX file only", 25 | ) 26 | ], 27 | verbose_name="Upload teacher List File(.csv/.xl)", 28 | ), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /classroom/migrations/0014_remove_teacher_classroom_classroom_teachers.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-10 05:47 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0013_alter_college_allowed_teacher_list"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="teacher", 15 | name="classroom", 16 | ), 17 | migrations.AddField( 18 | model_name="classroom", 19 | name="teachers", 20 | field=models.ManyToManyField( 21 | related_name="classrooms", to="classroom.teacher" 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /classroom/migrations/0015_alter_teacher_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-10 07:20 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 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("classroom", "0014_remove_teacher_classroom_classroom_teachers"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name="teacher", 18 | name="user", 19 | field=models.OneToOneField( 20 | default="9d1f0ef716ff4fc1a6380ecdd021e60f", 21 | on_delete=django.db.models.deletion.CASCADE, 22 | related_name="teacher_profile", 23 | to=settings.AUTH_USER_MODEL, 24 | ), 25 | preserve_default=False, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /classroom/migrations/0016_alter_classroom_teachers.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-11 05:49 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0015_alter_teacher_user"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="classroom", 15 | name="teachers", 16 | field=models.ManyToManyField( 17 | blank=True, related_name="classrooms", to="classroom.teacher" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /classroom/migrations/0017_allowedteacherclassroomlevel.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-11 06:45 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0016_alter_classroom_teachers"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="AllowedTeacherClassroomLevel", 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 | ("email", models.EmailField(max_length=255, verbose_name="Email Id")), 27 | ( 28 | "classroom", 29 | models.ForeignKey( 30 | on_delete=django.db.models.deletion.CASCADE, 31 | related_name="allowed_teachers", 32 | to="classroom.classroom", 33 | ), 34 | ), 35 | ], 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /classroom/migrations/0018_alter_classroom_teachers.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-11 15:54 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0017_allowedteacherclassroomlevel"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="classroom", 15 | name="teachers", 16 | field=models.ManyToManyField( 17 | blank=True, 18 | editable=False, 19 | related_name="classrooms", 20 | to="classroom.teacher", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /classroom/migrations/0019_alter_notesattachmentfile_title.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-11 18:54 2 | 3 | from django.db import migrations 4 | import django_extensions.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0018_alter_classroom_teachers"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="notesattachmentfile", 16 | name="title", 17 | field=django_extensions.db.fields.AutoSlugField( 18 | blank=True, 19 | default="a-1", 20 | editable=False, 21 | populate_from=["notes__title", "notes__subject__title", "created_at"], 22 | ), 23 | preserve_default=False, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /classroom/migrations/0020_alter_notesattachmentfile_file_path.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-12 12:18 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0019_alter_notesattachmentfile_title"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="notesattachmentfile", 16 | name="file_path", 17 | field=models.FileField( 18 | blank=True, 19 | max_length=400, 20 | null=True, 21 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/classroom/notes/%Y/%m/%d", 22 | validators=[ 23 | django.core.validators.FileExtensionValidator( 24 | allowed_extensions=["xlsx", "pdf", "doc"], 25 | message="Please Upload XLSX/PDF/Doc file only", 26 | ) 27 | ], 28 | verbose_name="Upload File Here", 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /classroom/migrations/0021_assignment.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-13 16:42 2 | 3 | import classroom.validators 4 | import datetime 5 | import django.core.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ("classroom", "0020_alter_notesattachmentfile_file_path"), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name="Assignment", 19 | fields=[ 20 | ( 21 | "id", 22 | models.BigAutoField( 23 | auto_created=True, 24 | primary_key=True, 25 | serialize=False, 26 | verbose_name="ID", 27 | ), 28 | ), 29 | ( 30 | "title", 31 | models.CharField(max_length=300, verbose_name="Assignment Title"), 32 | ), 33 | ("description", models.TextField(blank=True, null=True)), 34 | ( 35 | "attached_pdf", 36 | models.FileField( 37 | blank=True, 38 | max_length=500, 39 | null=True, 40 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/classroom/assignments/%Y/%m/%d", 41 | validators=[ 42 | django.core.validators.FileExtensionValidator( 43 | allowed_extensions=["pdf"], 44 | message="Please Upload PDF file only", 45 | ), 46 | classroom.validators.pdf_file_size_lt_5mb, 47 | ], 48 | verbose_name="Upload File Here", 49 | ), 50 | ), 51 | ( 52 | "due_date", 53 | models.DateField( 54 | default=datetime.date(2022, 5, 14), 55 | validators=[classroom.validators.assignment_date_gte_today], 56 | verbose_name="Due by", 57 | ), 58 | ), 59 | ( 60 | "due_time", 61 | models.TimeField( 62 | default=datetime.datetime(2022, 5, 13, 22, 12, 6, 961250), 63 | verbose_name="Due time", 64 | ), 65 | ), 66 | ( 67 | "created_at", 68 | models.DateTimeField(auto_now_add=True, verbose_name="Created At "), 69 | ), 70 | ( 71 | "given_by", 72 | models.ForeignKey( 73 | null=True, 74 | on_delete=django.db.models.deletion.SET_NULL, 75 | related_name="assignments_given", 76 | to="classroom.teacher", 77 | ), 78 | ), 79 | ( 80 | "subject", 81 | models.ForeignKey( 82 | on_delete=django.db.models.deletion.CASCADE, 83 | related_name="assignments", 84 | to="classroom.subject", 85 | ), 86 | ), 87 | ], 88 | ), 89 | ] 90 | -------------------------------------------------------------------------------- /classroom/migrations/0022_alter_assignment_options_alter_assignment_due_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-13 16:46 2 | 3 | import datetime 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0021_assignment"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterModelOptions( 15 | name="assignment", 16 | options={"ordering": ["due_date", "due_time", "-created_at"]}, 17 | ), 18 | migrations.AlterField( 19 | model_name="assignment", 20 | name="due_time", 21 | field=models.TimeField( 22 | default=datetime.datetime(2022, 5, 13, 22, 16, 11, 407031), 23 | verbose_name="Due time", 24 | ), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /classroom/migrations/0023_alter_assignment_options_assignment_alloted_marks_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-13 16:55 2 | 3 | import classroom.validators 4 | import datetime 5 | import django.core.validators 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("classroom", "0022_alter_assignment_options_alter_assignment_due_time"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterModelOptions( 17 | name="assignment", 18 | options={ 19 | "ordering": ["due_date", "due_time", "alloted_marks", "-created_at"] 20 | }, 21 | ), 22 | migrations.AddField( 23 | model_name="assignment", 24 | name="alloted_marks", 25 | field=models.PositiveSmallIntegerField( 26 | default=100, 27 | validators=[ 28 | django.core.validators.MaxValueValidator( 29 | 100, "so sir you want to take exam more than 100 marks?😑" 30 | ) 31 | ], 32 | verbose_name="Marks:", 33 | ), 34 | ), 35 | migrations.AlterField( 36 | model_name="assignment", 37 | name="attached_pdf", 38 | field=models.FileField( 39 | blank=True, 40 | max_length=500, 41 | null=True, 42 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/classroom/assignments/%Y/%m/%d", 43 | validators=[ 44 | django.core.validators.FileExtensionValidator( 45 | allowed_extensions=["pdf"], 46 | message="Please Upload PDF file only", 47 | ), 48 | classroom.validators.pdf_file_size_lt_5mb, 49 | ], 50 | verbose_name="Upload File Here📁", 51 | ), 52 | ), 53 | migrations.AlterField( 54 | model_name="assignment", 55 | name="due_time", 56 | field=models.TimeField( 57 | default=datetime.datetime(2022, 5, 13, 22, 25, 15, 927058), 58 | verbose_name="Due time", 59 | ), 60 | ), 61 | ] 62 | -------------------------------------------------------------------------------- /classroom/migrations/0024_delete_assignment.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-14 14:59 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ( 10 | "classroom", 11 | "0023_alter_assignment_options_assignment_alloted_marks_and_more", 12 | ), 13 | ] 14 | 15 | operations = [ 16 | migrations.DeleteModel( 17 | name="Assignment", 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /classroom/migrations/0025_collegedba.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-15 12:19 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 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ("classroom", "0024_delete_assignment"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="CollegeDBA", 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 | ( 29 | "college", 30 | models.ForeignKey( 31 | on_delete=django.db.models.deletion.CASCADE, 32 | related_name="college_dbas", 33 | to="classroom.college", 34 | unique=True, 35 | ), 36 | ), 37 | ( 38 | "user", 39 | models.OneToOneField( 40 | on_delete=django.db.models.deletion.CASCADE, 41 | related_name="college_dba", 42 | to=settings.AUTH_USER_MODEL, 43 | ), 44 | ), 45 | ], 46 | options={ 47 | "ordering": ["user__first_name", "user__last_name"], 48 | }, 49 | ), 50 | ] 51 | -------------------------------------------------------------------------------- /classroom/migrations/0026_alter_allowedteacher_email.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-15 12:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0025_collegedba"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="allowedteacher", 15 | name="email", 16 | field=models.EmailField( 17 | max_length=255, unique=True, verbose_name="Email Id" 18 | ), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /classroom/migrations/0027_allowedcollegedba.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-15 12:37 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0026_alter_allowedteacher_email"), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name="AllowedCollegeDBA", 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 | ( 27 | "email", 28 | models.EmailField( 29 | max_length=255, unique=True, verbose_name="Email Id" 30 | ), 31 | ), 32 | ( 33 | "college", 34 | models.ForeignKey( 35 | on_delete=django.db.models.deletion.CASCADE, 36 | related_name="allowed_dbas", 37 | to="classroom.college", 38 | ), 39 | ), 40 | ], 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /classroom/migrations/0028_alter_collegedba_college.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-15 12:39 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0027_allowedcollegedba"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="collegedba", 16 | name="college", 17 | field=models.OneToOneField( 18 | on_delete=django.db.models.deletion.CASCADE, 19 | related_name="college_dbas", 20 | to="classroom.college", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /classroom/migrations/0029_alter_college_allowed_teacher_list.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-16 13:25 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0028_alter_collegedba_college"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="college", 16 | name="allowed_teacher_list", 17 | field=models.FileField( 18 | blank=True, 19 | max_length=500, 20 | null=True, 21 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/classroom/teachers/", 22 | validators=[ 23 | django.core.validators.FileExtensionValidator( 24 | allowed_extensions=["csv", "xlsx"], 25 | message="Please Upload CSV/XLSX file only", 26 | ) 27 | ], 28 | verbose_name="Upload teacher List File(.csv/.xl)", 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /classroom/migrations/0030_college_allowed_dba_list.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-17 12:18 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0029_alter_college_allowed_teacher_list"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="college", 16 | name="allowed_dba_list", 17 | field=models.FileField( 18 | blank=True, 19 | max_length=500, 20 | null=True, 21 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/college/dbas/%Y/%m/%d", 22 | validators=[ 23 | django.core.validators.FileExtensionValidator( 24 | allowed_extensions=["csv", "xlsx"], 25 | message="Please Upload CSV/XLSX file only", 26 | ) 27 | ], 28 | verbose_name="Upload teacher List File(.csv/.xl)", 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /classroom/migrations/0031_college_owner_email_id_collegedba_is_owner.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-19 06:26 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0030_college_allowed_dba_list"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="college", 15 | name="owner_email_id", 16 | field=models.EmailField( 17 | default=None, 18 | max_length=254, 19 | unique=True, 20 | verbose_name="College Owner DBA Mail [unique]", 21 | ), 22 | preserve_default=False, 23 | ), 24 | migrations.AddField( 25 | model_name="collegedba", 26 | name="is_owner", 27 | field=models.BooleanField(default=False, verbose_name="Owner [✔/❌]"), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /classroom/migrations/0032_alter_collegedba_college.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-19 07:04 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0031_college_owner_email_id_collegedba_is_owner"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="collegedba", 16 | name="college", 17 | field=models.ForeignKey( 18 | on_delete=django.db.models.deletion.CASCADE, 19 | related_name="college_dbas", 20 | to="classroom.college", 21 | ), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /classroom/migrations/0033_alter_college_allowed_dba_list.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-19 11:13 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0032_alter_collegedba_college"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="college", 16 | name="allowed_dba_list", 17 | field=models.FileField( 18 | blank=True, 19 | max_length=500, 20 | null=True, 21 | upload_to="P:\\Codes\\SEM_4_Major_Project\\Code\\ClassroomBackend\\media/college/dbas/%Y/%m/%d", 22 | validators=[ 23 | django.core.validators.FileExtensionValidator( 24 | allowed_extensions=["csv", "xlsx"], 25 | message="Please Upload CSV/XLSX file only", 26 | ) 27 | ], 28 | verbose_name="Upload DBA List File(.csv/.xl)", 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /classroom/migrations/0034_alter_classroom_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-20 06:01 2 | 3 | from django.db import migrations 4 | import django_extensions.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0033_alter_college_allowed_dba_list"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="classroom", 16 | name="slug", 17 | field=django_extensions.db.fields.AutoSlugField( 18 | blank=True, 19 | editable=False, 20 | populate_from=[ 21 | "title", 22 | "level", 23 | "stream", 24 | "section", 25 | "start_year", 26 | "end_year", 27 | "college__name", 28 | ], 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /classroom/migrations/0035_alter_classroom_allowed_student_list_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-21 14:52 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0034_alter_classroom_slug"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="classroom", 16 | name="allowed_student_list", 17 | field=models.FileField( 18 | blank=True, 19 | null=True, 20 | upload_to="classroom/students/", 21 | validators=[ 22 | django.core.validators.FileExtensionValidator( 23 | allowed_extensions=["csv", "xlsx"], 24 | message="Please Upload CSV/XLSX file only", 25 | ) 26 | ], 27 | verbose_name="Upload Student List File(.csv/xl)", 28 | ), 29 | ), 30 | migrations.AlterField( 31 | model_name="classroom", 32 | name="allowed_teacher_list", 33 | field=models.FileField( 34 | blank=True, 35 | null=True, 36 | upload_to="classroom/teachers/", 37 | validators=[ 38 | django.core.validators.FileExtensionValidator( 39 | allowed_extensions=["csv", "xlsx"], 40 | message="Please Upload CSV/XLSX file only", 41 | ) 42 | ], 43 | verbose_name="Upload Teacher List File(.csv/xl)", 44 | ), 45 | ), 46 | migrations.AlterField( 47 | model_name="college", 48 | name="allowed_dba_list", 49 | field=models.FileField( 50 | blank=True, 51 | max_length=500, 52 | null=True, 53 | upload_to="college/dbas/%Y/%m/%d", 54 | validators=[ 55 | django.core.validators.FileExtensionValidator( 56 | allowed_extensions=["csv", "xlsx"], 57 | message="Please Upload CSV/XLSX file only", 58 | ) 59 | ], 60 | verbose_name="Upload DBA List File(.csv/.xl)", 61 | ), 62 | ), 63 | migrations.AlterField( 64 | model_name="college", 65 | name="allowed_teacher_list", 66 | field=models.FileField( 67 | blank=True, 68 | max_length=500, 69 | null=True, 70 | upload_to="classroom/teachers/", 71 | validators=[ 72 | django.core.validators.FileExtensionValidator( 73 | allowed_extensions=["csv", "xlsx"], 74 | message="Please Upload CSV/XLSX file only", 75 | ) 76 | ], 77 | verbose_name="Upload teacher List File(.csv/.xl)", 78 | ), 79 | ), 80 | migrations.AlterField( 81 | model_name="notesattachmentfile", 82 | name="file_path", 83 | field=models.FileField( 84 | blank=True, 85 | max_length=400, 86 | null=True, 87 | upload_to="classroom/notes/%Y/%m/%d", 88 | validators=[ 89 | django.core.validators.FileExtensionValidator( 90 | allowed_extensions=["xlsx", "pdf", "doc"], 91 | message="Please Upload XLSX/PDF/Doc file only", 92 | ) 93 | ], 94 | verbose_name="Upload File Here", 95 | ), 96 | ), 97 | ] 98 | -------------------------------------------------------------------------------- /classroom/migrations/0036_college_stream_list_stream.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-21 15:53 2 | 3 | import django.core.validators 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("classroom", "0035_alter_classroom_allowed_student_list_and_more"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="college", 17 | name="stream_list", 18 | field=models.FileField( 19 | blank=True, 20 | max_length=500, 21 | null=True, 22 | upload_to="college/streams/", 23 | validators=[ 24 | django.core.validators.FileExtensionValidator( 25 | allowed_extensions=["csv", "xlsx"], 26 | message="Please Upload CSV/XLSX file only", 27 | ) 28 | ], 29 | verbose_name="Upload Stream List File(.csv/.xl)", 30 | ), 31 | ), 32 | migrations.CreateModel( 33 | name="Stream", 34 | fields=[ 35 | ( 36 | "id", 37 | models.BigAutoField( 38 | auto_created=True, 39 | primary_key=True, 40 | serialize=False, 41 | verbose_name="ID", 42 | ), 43 | ), 44 | ("title", models.CharField(max_length=255)), 45 | ( 46 | "college", 47 | models.ForeignKey( 48 | on_delete=django.db.models.deletion.CASCADE, 49 | related_name="streams", 50 | to="classroom.college", 51 | ), 52 | ), 53 | ( 54 | "dba", 55 | models.ForeignKey( 56 | blank=True, 57 | null=True, 58 | on_delete=django.db.models.deletion.CASCADE, 59 | related_name="streams", 60 | to="classroom.collegedba", 61 | ), 62 | ), 63 | ], 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /classroom/migrations/0037_alter_stream_options_alter_subject_credit_points.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-21 16:10 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0036_college_stream_list_stream"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="stream", 15 | options={"ordering": ["college__name", "title"]}, 16 | ), 17 | migrations.AlterField( 18 | model_name="subject", 19 | name="credit_points", 20 | field=models.PositiveSmallIntegerField( 21 | choices=[ 22 | (1, "1"), 23 | (2, "2"), 24 | (3, "3"), 25 | (4, "4"), 26 | (5, "5"), 27 | (6, "6"), 28 | (7, "7"), 29 | (8, "8"), 30 | (9, "9"), 31 | (10, "10"), 32 | (11, "11"), 33 | (12, "12"), 34 | (13, "13"), 35 | (14, "14"), 36 | (15, "15"), 37 | (16, "16"), 38 | (17, "17"), 39 | (18, "18"), 40 | (19, "19"), 41 | (20, "20"), 42 | ], 43 | default=1, 44 | ), 45 | ), 46 | ] 47 | -------------------------------------------------------------------------------- /classroom/migrations/0038_alter_subject_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-22 09:59 2 | 3 | from django.db import migrations 4 | import django_extensions.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0037_alter_stream_options_alter_subject_credit_points"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="subject", 16 | name="slug", 17 | field=django_extensions.db.fields.AutoSlugField( 18 | blank=True, 19 | editable=False, 20 | populate_from=[ 21 | "title", 22 | "semester__sem_no", 23 | "subject_type", 24 | "credit_points", 25 | "created_by__user__first_name", 26 | ], 27 | ), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /classroom/migrations/0039_alter_subject_slug.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-22 10:01 2 | 3 | from django.db import migrations 4 | import django_extensions.db.fields 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("classroom", "0038_alter_subject_slug"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name="subject", 16 | name="slug", 17 | field=django_extensions.db.fields.AutoSlugField( 18 | blank=True, 19 | editable=False, 20 | populate_from=[ 21 | "title", 22 | "semester__sem_no", 23 | "subject_type", 24 | "credit_points", 25 | "created_by__user__first_name", 26 | "created_by__user__last_name", 27 | ], 28 | ), 29 | ), 30 | ] 31 | -------------------------------------------------------------------------------- /classroom/migrations/0040_assignment.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-22 13:28 2 | 3 | import classroom.validators 4 | import datetime 5 | import django.core.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ("classroom", "0039_alter_subject_slug"), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name="Assignment", 19 | fields=[ 20 | ( 21 | "id", 22 | models.BigAutoField( 23 | auto_created=True, 24 | primary_key=True, 25 | serialize=False, 26 | verbose_name="ID", 27 | ), 28 | ), 29 | ( 30 | "title", 31 | models.CharField(max_length=300, verbose_name="Assignment Title"), 32 | ), 33 | ("description", models.TextField(blank=True, null=True)), 34 | ( 35 | "alloted_marks", 36 | models.PositiveSmallIntegerField( 37 | default=100, 38 | validators=[ 39 | django.core.validators.MaxValueValidator( 40 | 100, 41 | "assignments can't be alloted more than 100 marks 😑", 42 | ) 43 | ], 44 | verbose_name="Marks:", 45 | ), 46 | ), 47 | ( 48 | "attached_pdf", 49 | models.FileField( 50 | blank=True, 51 | max_length=500, 52 | null=True, 53 | upload_to="classroom/assignments/", 54 | validators=[ 55 | django.core.validators.FileExtensionValidator( 56 | allowed_extensions=["pdf"], 57 | message="Please Upload PDF file only", 58 | ), 59 | classroom.validators.pdf_file_size_lt_5mb, 60 | ], 61 | verbose_name="Upload File Here📁", 62 | ), 63 | ), 64 | ( 65 | "due_date", 66 | models.DateField( 67 | default=datetime.date(2022, 5, 23), 68 | validators=[classroom.validators.assignment_date_gte_today], 69 | verbose_name="Due by", 70 | ), 71 | ), 72 | ( 73 | "due_time", 74 | models.TimeField( 75 | default=datetime.time(13, 28, 5, 829323), 76 | verbose_name="Due time", 77 | ), 78 | ), 79 | ( 80 | "created_at", 81 | models.DateTimeField(auto_now_add=True, verbose_name="Created At "), 82 | ), 83 | ( 84 | "given_by", 85 | models.ForeignKey( 86 | null=True, 87 | on_delete=django.db.models.deletion.SET_NULL, 88 | related_name="assignments_given", 89 | to="classroom.teacher", 90 | ), 91 | ), 92 | ( 93 | "subject", 94 | models.ForeignKey( 95 | on_delete=django.db.models.deletion.CASCADE, 96 | related_name="assignments", 97 | to="classroom.subject", 98 | ), 99 | ), 100 | ], 101 | options={ 102 | "ordering": ["due_date", "due_time", "alloted_marks", "-created_at"], 103 | }, 104 | ), 105 | ] 106 | -------------------------------------------------------------------------------- /classroom/migrations/0041_alter_assignment_due_time_submittedassignment.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-22 13:35 2 | 3 | import classroom.validators 4 | import datetime 5 | import django.core.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ("classroom", "0040_assignment"), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name="assignment", 19 | name="due_time", 20 | field=models.TimeField( 21 | default=datetime.time(13, 35, 25, 382904), verbose_name="Due time" 22 | ), 23 | ), 24 | migrations.CreateModel( 25 | name="SubmittedAssignment", 26 | fields=[ 27 | ( 28 | "id", 29 | models.BigAutoField( 30 | auto_created=True, 31 | primary_key=True, 32 | serialize=False, 33 | verbose_name="ID", 34 | ), 35 | ), 36 | ("answer_section", models.TextField(blank=True, null=True)), 37 | ( 38 | "submitted_file", 39 | models.FileField( 40 | blank=True, 41 | max_length=500, 42 | null=True, 43 | upload_to="classroom/assignment_submissions/", 44 | validators=[ 45 | django.core.validators.FileExtensionValidator( 46 | allowed_extensions=["pdf"], 47 | message="Please Upload PDF file only", 48 | ), 49 | classroom.validators.pdf_file_size_lt_5mb, 50 | ], 51 | verbose_name="Upload File Here📁", 52 | ), 53 | ), 54 | ( 55 | "is_submitted", 56 | models.BooleanField(default=False, verbose_name="submitted : "), 57 | ), 58 | ("submission_date", models.DateField(auto_now_add=True)), 59 | ("submission_time", models.TimeField(auto_now_add=True)), 60 | ( 61 | "score", 62 | models.IntegerField( 63 | default=0, 64 | validators=[ 65 | django.core.validators.MinValueValidator( 66 | 0, "Score should be >= 0" 67 | ), 68 | django.core.validators.MaxValueValidator( 69 | 100, "score should be <= 100" 70 | ), 71 | ], 72 | verbose_name="0<=x<=100", 73 | ), 74 | ), 75 | ( 76 | "has_scored", 77 | models.BooleanField( 78 | default=False, verbose_name="Scored by teacher : " 79 | ), 80 | ), 81 | ( 82 | "remarks", 83 | models.TextField( 84 | blank=True, max_length=400, null=True, verbose_name="remarks" 85 | ), 86 | ), 87 | ( 88 | "assignment", 89 | models.ForeignKey( 90 | on_delete=django.db.models.deletion.CASCADE, 91 | related_name="submissions", 92 | to="classroom.assignment", 93 | ), 94 | ), 95 | ( 96 | "scored_by", 97 | models.ForeignKey( 98 | blank=True, 99 | null=True, 100 | on_delete=django.db.models.deletion.SET_NULL, 101 | related_name="scored_assignments", 102 | to="classroom.teacher", 103 | ), 104 | ), 105 | ( 106 | "submitted_by", 107 | models.ForeignKey( 108 | on_delete=django.db.models.deletion.CASCADE, 109 | related_name="attempted_assignments", 110 | to="classroom.student", 111 | ), 112 | ), 113 | ], 114 | options={ 115 | "ordering": ["-submission_date", "-submission_time", "-score"], 116 | "unique_together": {("assignment", "submitted_by")}, 117 | }, 118 | ), 119 | ] 120 | -------------------------------------------------------------------------------- /classroom/migrations/0042_alter_assignment_due_date_alter_assignment_due_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-22 13:39 2 | 3 | import classroom.validators 4 | from django.db import migrations, models 5 | import django.utils.timezone 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("classroom", "0041_alter_assignment_due_time_submittedassignment"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name="assignment", 17 | name="due_date", 18 | field=models.DateField( 19 | default=django.utils.timezone.now, 20 | validators=[classroom.validators.assignment_date_gte_today], 21 | verbose_name="Due by", 22 | ), 23 | ), 24 | migrations.AlterField( 25 | model_name="assignment", 26 | name="due_time", 27 | field=models.TimeField( 28 | default=django.utils.timezone.now, verbose_name="Due time" 29 | ), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /classroom/migrations/0043_rename_submittedassignment_assignmentsubmission.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-22 13:47 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0042_alter_assignment_due_date_alter_assignment_due_time"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameModel( 14 | old_name="SubmittedAssignment", 15 | new_name="AssignmentSubmission", 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /classroom/migrations/0044_remove_assignment_given_by_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-22 15:24 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0043_rename_submittedassignment_assignmentsubmission"), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name="assignment", 15 | name="given_by", 16 | ), 17 | migrations.RemoveField( 18 | model_name="assignmentsubmission", 19 | name="scored_by", 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /classroom/migrations/0045_alter_college_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-23 11:06 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0044_remove_assignment_given_by_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="college", 15 | options={"ordering": ["name", "city"]}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /classroom/migrations/0046_alter_subject_options.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-23 12:35 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0045_alter_college_options"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name="subject", 15 | options={"ordering": ["subject_code", "title", "-subject_type"]}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /classroom/migrations/0047_alter_stream_unique_together.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-28 16:41 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0046_alter_subject_options"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name="stream", 15 | unique_together={("title", "dba")}, 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /classroom/migrations/0048_alter_allowedstudents_unique_together_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-05-29 16:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0047_alter_stream_unique_together"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterUniqueTogether( 14 | name="allowedstudents", 15 | unique_together=set(), 16 | ), 17 | migrations.AlterField( 18 | model_name="allowedstudents", 19 | name="email", 20 | field=models.EmailField( 21 | max_length=255, unique=True, verbose_name="Email Id" 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /classroom/migrations/0049_alter_classroom_section.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.4 on 2022-06-02 18:29 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("classroom", "0048_alter_allowedstudents_unique_together_and_more"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name="classroom", 15 | name="section", 16 | field=models.CharField( 17 | blank=True, 18 | default="A", 19 | max_length=5, 20 | null=True, 21 | verbose_name="Section(optional)", 22 | ), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /classroom/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/classroom/migrations/__init__.py -------------------------------------------------------------------------------- /classroom/model.py: -------------------------------------------------------------------------------- 1 | from classroom.models.college import College, AllowedTeacher, AllowedCollegeDBA 2 | from classroom.models.classroom import ( 3 | Classroom, 4 | Semester, 5 | AllowedTeacherClassroomLevel, 6 | AllowedStudents, 7 | ) 8 | from classroom.models.teacher import Teacher 9 | from classroom.models.student import Student 10 | from classroom.models.subject import Subject 11 | from classroom.models.notes import Notes, NotesAttachmentFile 12 | from classroom.models.announcement import Announcement 13 | from classroom.models.imports import User 14 | from classroom.models.college_dba import CollegeDBA 15 | from classroom.models.assignment import Assignment, AssignmentSubmission 16 | -------------------------------------------------------------------------------- /classroom/models/__init__.py: -------------------------------------------------------------------------------- 1 | from . import * 2 | -------------------------------------------------------------------------------- /classroom/models/announcement.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class Announcement(models.Model): 6 | heading = models.TextField(_("Heading"), default="No Heading Given") 7 | body = models.TextField(_("Description[Optional] "), null=True, blank=True) 8 | created_at = models.DateTimeField(_("Created At "), auto_now_add=True) 9 | updated_at = models.DateTimeField(_("Updated At "), auto_now=True) 10 | subject = models.ForeignKey( 11 | "classroom.Subject", on_delete=models.CASCADE, related_name="announcements" 12 | ) 13 | posted_by = models.ForeignKey( 14 | "classroom.Teacher", on_delete=models.CASCADE, related_name="announcements" 15 | ) 16 | 17 | class Meta: 18 | ordering = ["-updated_at", "-created_at"] 19 | 20 | def __str__(self) -> str: 21 | return f"{self.heading}" 22 | 23 | def heading_short(self): 24 | return f"{self.heading[:10]}..." 25 | -------------------------------------------------------------------------------- /classroom/models/assignment.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | from django.utils.timezone import now, timedelta 4 | 5 | 6 | class Assignment(models.Model): 7 | title = models.CharField(_("Assignment Title"), max_length=300) 8 | description = models.TextField(null=True, blank=True) 9 | alloted_marks = models.PositiveSmallIntegerField( 10 | _("Marks:"), 11 | default=100, 12 | validators=[ 13 | MaxValueValidator(100, "assignments can't be alloted more than 100 marks 😑") 14 | ], 15 | ) 16 | attached_pdf = models.FileField( 17 | _("Upload File Here📁"), 18 | null=True, 19 | blank=True, 20 | max_length=500, 21 | upload_to="classroom/assignments/", 22 | validators=[ 23 | FileExtensionValidator( 24 | allowed_extensions=["pdf"], 25 | message="Please Upload PDF file only", 26 | ), 27 | pdf_file_size_lt_5mb, 28 | ], 29 | ) 30 | due_date = models.DateField( 31 | _("Due by"), 32 | default=now, 33 | validators=[assignment_date_gte_today], 34 | ) 35 | due_time = models.TimeField(_("Due time"), default=now) 36 | subject = models.ForeignKey( 37 | "classroom.Subject", on_delete=models.CASCADE, related_name="assignments" 38 | ) 39 | # given_by = models.ForeignKey( 40 | # "classroom.Teacher", 41 | # on_delete=models.SET_NULL, 42 | # null=True, 43 | # related_name="assignments_given", 44 | # ) 45 | 46 | created_at = models.DateTimeField( 47 | _("Created At "), auto_now_add=True, editable=False 48 | ) 49 | 50 | class Meta: 51 | ordering = ["due_date", "due_time", "alloted_marks", "-created_at"] 52 | 53 | def __str__(self) -> str: 54 | return self.title 55 | 56 | def file_path(self): 57 | return self.attached_pdf.name 58 | 59 | def short_description(self) -> str: 60 | return self.description[:30] 61 | 62 | 63 | class AssignmentSubmission(models.Model): 64 | # FK to assignment 65 | assignment = models.ForeignKey( 66 | "classroom.Assignment", on_delete=models.CASCADE, related_name="submissions" 67 | ) 68 | # ----------------FOR STUDENT ---------------------------------------------- 69 | submitted_by = models.ForeignKey( 70 | "classroom.Student", 71 | on_delete=models.CASCADE, 72 | related_name="attempted_assignments", 73 | ) 74 | answer_section = models.TextField(null=True, blank=True) 75 | submitted_file = models.FileField( 76 | _("Upload File Here📁"), 77 | null=True, 78 | blank=True, 79 | max_length=500, 80 | upload_to=f"classroom/assignment_submissions/", 81 | validators=[ 82 | FileExtensionValidator( 83 | allowed_extensions=["pdf"], 84 | message="Please Upload PDF file only", 85 | ), 86 | pdf_file_size_lt_5mb, 87 | ], 88 | ) 89 | is_submitted = models.BooleanField(_("submitted : "), default=False) 90 | submission_date = models.DateField(auto_now_add=True, editable=False) 91 | submission_time = models.TimeField(auto_now_add=True, editable=False) 92 | 93 | # ------------- FOR TEACHER Control ------------- 94 | score = models.IntegerField( 95 | _("0<=x<=100"), 96 | default=0, 97 | validators=[ 98 | MinValueValidator(0, "Score should be >= 0"), 99 | MaxValueValidator( 100 | 100, 101 | "score should be <= 100", 102 | ), 103 | ], 104 | ) 105 | has_scored = models.BooleanField(_("Scored by teacher : "), default=False) 106 | remarks = models.TextField(_("remarks"), blank=True, null=True, max_length=400) 107 | # scored_by = models.ForeignKey( 108 | # "classroom.Teacher", 109 | # on_delete=models.SET_NULL, 110 | # related_name="scored_assignments", 111 | # blank=True, 112 | # null=True, 113 | # ) 114 | 115 | class Meta: 116 | unique_together = [["assignment", "submitted_by"]] 117 | ordering = ["-submission_date", "-submission_time", "-score"] 118 | 119 | def __str__(self) -> str: 120 | return f"{self.submitted_by}" 121 | -------------------------------------------------------------------------------- /classroom/models/classroom.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class Classroom(models.Model): 6 | slug = AutoSlugField( 7 | populate_from=[ 8 | "title", 9 | "level", 10 | "stream", 11 | "section", 12 | "start_year", 13 | "end_year", 14 | "college__name", 15 | ], 16 | ) 17 | title = models.CharField(_("Classroom Name"), max_length=255, null=True, blank=True) 18 | level = models.CharField( 19 | _("Level"), 20 | max_length=40, 21 | choices=LEVEL_CHOICES.choices, 22 | default=LEVEL_CHOICES.UnderGraduate, 23 | help_text="e.g - Masters/Bachelors", 24 | ) 25 | stream = models.CharField( 26 | _("Your Stream"), 27 | max_length=255, 28 | ) # TODO: Make Drop Down 29 | start_year = models.PositiveSmallIntegerField( 30 | _("Starting Year"), 31 | # default=2022, 32 | default=date.today().year - 2, 33 | db_index=True, 34 | help_text="Write your session starting year (e.g. - 2020)", 35 | validators=[ 36 | MinValueValidator(2000, "You can't select year less than 2000"), 37 | MaxValueValidator( 38 | (date.today().year + 1), # FIXME: Make this dynamic 39 | # 2023, 40 | "Max Year Can be selected only 1 year ahead of current year", 41 | ), 42 | ], 43 | ) 44 | end_year = models.PositiveSmallIntegerField( 45 | _("Ending Year"), 46 | db_index=True, 47 | default=date.today().year, 48 | help_text="Write your session ending year (e.g. - 2020)", 49 | validators=[ 50 | MinValueValidator(2000, "You can't select year less than 2000"), 51 | MaxValueValidator( 52 | (2200), # FIXME: Make this dynamic 53 | "Max Year Can be selected only 1 year ahead of current year", 54 | ), 55 | ], 56 | ) 57 | section = models.CharField( 58 | _("Section(optional)"), 59 | max_length=5, 60 | null=True, 61 | blank=True, 62 | default=SECTION_CHOICES.A, 63 | ) 64 | no_of_semesters = models.PositiveSmallIntegerField( 65 | _("Number of Sem"), 66 | default=4, 67 | validators=[ 68 | MinValueValidator(4, "Min Course Duration is of 2 Years(4 semesters)"), 69 | MaxValueValidator(14), 70 | is_no_of_sem_even, 71 | ], 72 | ) 73 | current_sem = models.PositiveSmallIntegerField( 74 | _("On Going Sem"), 75 | validators=[ 76 | MinValueValidator(1), 77 | MaxValueValidator(14), 78 | ], 79 | ) 80 | created_at = models.DateTimeField(auto_now_add=True, db_index=True) 81 | college = models.ForeignKey( 82 | "classroom.College", on_delete=models.CASCADE, related_name="classrooms" 83 | ) 84 | allowed_student_list = models.FileField( 85 | _("Upload Student List File(.csv/xl)"), 86 | null=True, 87 | blank=True, 88 | upload_to=f"classroom/students/", 89 | validators=[ 90 | FileExtensionValidator( 91 | allowed_extensions=["csv", "xlsx"], 92 | message="Please Upload CSV/XLSX file only", 93 | ) 94 | ], 95 | ) 96 | teachers = models.ManyToManyField( 97 | "classroom.Teacher", related_name="classrooms", blank=True, editable=False 98 | ) 99 | # TODO: use this to add teachers in classrooms and vice-versa 100 | allowed_teacher_list = models.FileField( 101 | _("Upload Teacher List File(.csv/xl)"), 102 | upload_to=f"classroom/teachers/", 103 | null=True, 104 | blank=True, 105 | validators=[ 106 | FileExtensionValidator( 107 | allowed_extensions=["csv", "xlsx"], 108 | message="Please Upload CSV/XLSX file only", 109 | ) 110 | ], 111 | ) 112 | 113 | class Meta: 114 | unique_together = [ 115 | "level", 116 | "stream", 117 | "start_year", 118 | "end_year", 119 | "section", 120 | "college", 121 | ] 122 | ordering = [ 123 | "college__name", 124 | "level", 125 | "-start_year", 126 | "-end_year", 127 | "section", 128 | "stream", 129 | ] 130 | 131 | def __str__(self) -> str: 132 | return str(self.title) 133 | 134 | 135 | class Semester(models.Model): 136 | classroom = models.ForeignKey( 137 | "classroom.Classroom", on_delete=models.CASCADE, related_name="semesters" 138 | ) 139 | sem_no = models.PositiveSmallIntegerField( 140 | _("Semester No"), 141 | # editable=False, 142 | validators=[ 143 | MinValueValidator(1, "sem value > 0"), 144 | MaxValueValidator(14, "sem value < 15"), 145 | ], 146 | ) 147 | is_current_sem = models.BooleanField(_("is this sem going on? "), default=False) 148 | 149 | class Meta: 150 | unique_together = ["classroom", "sem_no"] 151 | ordering = ["classroom__title", "sem_no"] 152 | 153 | def __str__(self) -> str: 154 | return str(self.sem_no) 155 | 156 | @admin.display(ordering=["classroom__title"]) 157 | def classroom_name(self): 158 | return self.classroom.title 159 | 160 | 161 | class AllowedTeacherClassroomLevel(models.Model): 162 | objects = AllowedTeacherClassroomLevelManager() 163 | email = models.EmailField(_("Email Id"), max_length=255) 164 | classroom = models.ForeignKey( 165 | "classroom.Classroom", on_delete=models.CASCADE, related_name="allowed_teachers" 166 | ) 167 | 168 | def __str__(self) -> str: 169 | return f"{self.email}" 170 | 171 | 172 | class AllowedStudents(models.Model): 173 | email = models.EmailField(_("Email Id"), max_length=255, unique=True) 174 | university_roll = models.PositiveBigIntegerField( 175 | _("University Roll"), 176 | help_text="Your University Roll No - (e.g. - 13071020030)", 177 | ) 178 | classroom = models.ForeignKey( 179 | "classroom.Classroom", on_delete=models.CASCADE, related_name="allowed_students" 180 | ) 181 | 182 | class Meta: 183 | ordering = ["university_roll"] 184 | verbose_name_plural = "Allowed Students" 185 | 186 | def __str__(self) -> str: 187 | return f"{self.email} || {self.university_roll}" 188 | 189 | @admin.display(ordering=["classroom__title"]) 190 | def get_classroom_name(self): 191 | return self.classroom.title 192 | -------------------------------------------------------------------------------- /classroom/models/college.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class College(models.Model): 6 | """ 7 | # College 8 | > `params`: 9 | > ('slug', 'name', 'city', 'state', 'address', 'allowed_teacher_list', 'allowed_dba_list','owner_email_id') 10 | """ 11 | 12 | slug = AutoSlugField( 13 | populate_from=["name", "state", "city"], 14 | editable=True, 15 | ) 16 | name = models.CharField(_("College Name"), max_length=255) 17 | city = models.CharField(_("City"), max_length=255) 18 | state = models.CharField(_("State"), max_length=255) 19 | address = models.TextField(null=True, blank=True) 20 | owner_email_id = models.EmailField( 21 | _("College Owner DBA Mail [unique]"), unique=True 22 | ) 23 | stream_list = models.FileField( 24 | _("Upload Stream List File(.csv/.xl)"), 25 | upload_to="college/streams/", 26 | null=True, 27 | max_length=500, 28 | blank=True, 29 | validators=[ 30 | FileExtensionValidator( 31 | allowed_extensions=["csv", "xlsx"], 32 | message="Please Upload CSV/XLSX file only", 33 | ) 34 | ], 35 | ) 36 | allowed_teacher_list = models.FileField( 37 | _("Upload teacher List File(.csv/.xl)"), 38 | upload_to="classroom/teachers/", 39 | null=True, 40 | max_length=500, 41 | blank=True, 42 | validators=[ 43 | FileExtensionValidator( 44 | allowed_extensions=["csv", "xlsx"], 45 | message="Please Upload CSV/XLSX file only", 46 | ) 47 | ], 48 | ) 49 | allowed_dba_list = ( 50 | models.FileField( # TODO: Allowed DBA Auto Create Signal by College 51 | _("Upload DBA List File(.csv/.xl)"), 52 | upload_to="college/dbas/%Y/%m/%d", 53 | null=True, 54 | max_length=500, 55 | blank=True, 56 | validators=[ 57 | FileExtensionValidator( 58 | allowed_extensions=["csv", "xlsx"], 59 | message="Please Upload CSV/XLSX file only", 60 | ) 61 | ], 62 | ) 63 | ) 64 | 65 | class Meta: 66 | ordering = ["name", "city"] 67 | 68 | def __str__(self) -> str: 69 | return f"{self.name} - {self.city}" 70 | 71 | 72 | class AllowedTeacher(models.Model): 73 | email = models.EmailField(_("Email Id"), max_length=255, unique=True) 74 | college = models.ForeignKey( 75 | "classroom.College", on_delete=models.CASCADE, related_name="allowed_teachers" 76 | ) 77 | 78 | def __str__(self) -> str: 79 | return f"{self.email}" 80 | 81 | 82 | class AllowedCollegeDBA(models.Model): 83 | email = models.EmailField(_("Email Id"), max_length=255, unique=True) 84 | college = models.ForeignKey( 85 | "classroom.College", on_delete=models.CASCADE, related_name="allowed_dbas" 86 | ) 87 | 88 | def __str__(self) -> str: 89 | return f"{self.email}" 90 | 91 | 92 | class Stream(models.Model): 93 | title = models.CharField(max_length=255) 94 | college = models.ForeignKey( 95 | "classroom.College", on_delete=models.CASCADE, related_name="streams" 96 | ) 97 | dba = models.ForeignKey( 98 | "classroom.CollegeDBA", 99 | on_delete=models.CASCADE, 100 | related_name="streams", 101 | null=True, 102 | blank=True, 103 | ) 104 | 105 | class Meta: 106 | ordering = ["college__name", "title"] 107 | unique_together = ["title", "dba"] 108 | 109 | def __str__(self) -> str: 110 | return f"{self.title} || Clg: {self.college.name}" 111 | -------------------------------------------------------------------------------- /classroom/models/college_dba.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class CollegeDBA(models.Model): 6 | is_owner = models.BooleanField(_("Owner [✔/❌]"), default=False) 7 | user = models.OneToOneField( 8 | User, on_delete=models.CASCADE, related_name="college_dba" 9 | ) 10 | college = models.ForeignKey( 11 | "classroom.College", 12 | on_delete=models.CASCADE, 13 | related_name="college_dbas", 14 | ) 15 | 16 | class Meta: 17 | ordering = ["user__first_name", "user__last_name"] 18 | 19 | def __str__(self) -> str: 20 | return f"{self.user.first_name} {self.user.last_name} | {self.user.email} |DBA" 21 | 22 | @admin.display(ordering=["user__email"]) 23 | def get_email(self): 24 | return self.user.email 25 | 26 | @admin.display(ordering=["user__first_name"]) 27 | def get_first_name(self): 28 | return self.user.first_name 29 | 30 | @admin.display(ordering=["user__last_name"]) 31 | def get_last_name(self): 32 | return self.user.last_name 33 | -------------------------------------------------------------------------------- /classroom/models/imports.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime, timedelta 2 | from random import randint 3 | from time import time 4 | 5 | from django.conf import settings 6 | from django.contrib import admin 7 | from django.contrib.auth import get_user_model 8 | from django.core.validators import ( 9 | FileExtensionValidator, 10 | MaxValueValidator, 11 | MinValueValidator, 12 | ) 13 | from django.db import models 14 | 15 | from django.utils.translation import gettext_lazy as _ 16 | from django_extensions.db.fields import AutoSlugField 17 | 18 | from classroom.constants import LEVEL_CHOICES, SECTION_CHOICES 19 | from classroom.managers import AllowedTeacherClassroomLevelManager 20 | from classroom.validators import ( 21 | assignment_date_gte_today, 22 | is_no_of_sem_even, 23 | pdf_file_size_lt_5mb, 24 | ) 25 | 26 | 27 | User = get_user_model() 28 | -------------------------------------------------------------------------------- /classroom/models/notes.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class Notes(models.Model): 6 | slug = AutoSlugField( 7 | populate_from=["title", "subject__title", "posted_by__user__first_name"] 8 | ) 9 | title = models.CharField(_("Title"), max_length=255) 10 | description = models.TextField(_("Description[optional]"), null=True, blank=True) 11 | created_at = models.DateTimeField(_("Created At "), auto_now_add=True) 12 | updated_at = models.DateTimeField(_("Updated At "), auto_now=True) 13 | subject = models.ForeignKey( 14 | "classroom.Subject", on_delete=models.CASCADE, related_name="notes" 15 | ) 16 | posted_by = models.ForeignKey( 17 | "classroom.Teacher", 18 | on_delete=models.SET_NULL, 19 | related_name="created_notes", 20 | null=True, 21 | ) 22 | 23 | class Meta: 24 | verbose_name_plural = "Notes" 25 | ordering = ["title", "-created_at"] 26 | 27 | def __str__(self) -> str: 28 | return self.title 29 | 30 | @admin.display(ordering="description") 31 | def short_description(self): 32 | return self.description[:40] 33 | 34 | 35 | class NotesAttachmentFile(models.Model): 36 | title = AutoSlugField( 37 | populate_from=["notes__title", "notes__subject__title", "created_at"], 38 | ) 39 | file_path = models.FileField( 40 | _("Upload File Here"), 41 | null=True, 42 | blank=True, 43 | max_length=400, 44 | upload_to=f"classroom/notes/%Y/%m/%d", 45 | validators=[ 46 | FileExtensionValidator( 47 | allowed_extensions=["xlsx", "pdf", "doc"], 48 | message="Please Upload XLSX/PDF/Doc file only", 49 | ) 50 | ], 51 | ) 52 | created_at = models.DateTimeField(_("Created At "), auto_now_add=True) 53 | notes = models.ForeignKey( 54 | "classroom.Notes", on_delete=models.CASCADE, related_name="attached_files" 55 | ) 56 | 57 | class Meta: 58 | ordering = ["id"] 59 | 60 | def __str__(self) -> str: 61 | return str(self.file_path.name) 62 | -------------------------------------------------------------------------------- /classroom/models/student.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class Student(models.Model): 6 | 7 | university_roll = models.PositiveBigIntegerField( 8 | _("University Roll"), 9 | help_text="Your University Roll No - (e.g. - 13071020030)", 10 | null=True, 11 | blank=True, 12 | ) 13 | 14 | user = models.OneToOneField( 15 | User, 16 | on_delete=models.CASCADE, 17 | null=True, 18 | blank=True, 19 | related_name="student_profile", 20 | ) 21 | 22 | classroom = models.ForeignKey( 23 | "classroom.Classroom", 24 | on_delete=models.CASCADE, 25 | related_name="students", 26 | null=True, 27 | blank=True, 28 | ) 29 | 30 | class Meta: 31 | ordering = ["user__first_name", "user__last_name"] 32 | 33 | def __str__(self) -> str: 34 | return f"{self.id}" 35 | 36 | @admin.display(ordering=["user__email"]) 37 | def get_email(self): 38 | return self.user.email 39 | 40 | @admin.display(ordering="user__first_name") 41 | def first_name(self): 42 | return self.user.first_name 43 | 44 | @admin.display(ordering="user__last_name") 45 | def last_name(self): 46 | return self.user.last_name 47 | 48 | @admin.display(ordering="classroom__college__name") 49 | def college_name(self): 50 | return self.classroom.college.name 51 | -------------------------------------------------------------------------------- /classroom/models/subject.py: -------------------------------------------------------------------------------- 1 | from django.utils.translation import gettext_lazy as _ 2 | from .imports import * 3 | 4 | 5 | class Subject(models.Model): 6 | """ 7 | # Subject belongs to semester 8 | - **slug** will be used as filter key 9 | - **subject_code** is [`optional`] 10 | - **title** is required 11 | - **subject_type** is required [Practical/Theory/Elective] 12 | - **credit_points** [`optional`] type: positive small integer 13 | - **credit_points** [`optional`] type: positive small integer 14 | """ 15 | 16 | # --------------Choice Fields------------------ 17 | THEORY = "TH" 18 | PRACTICAL = "PRC" 19 | ELECTIVE = "ELC" 20 | PROJECT = "PRJ" 21 | SUBJECT_TYPE_CHOICE = [ 22 | (THEORY, _("Theory")), 23 | (PRACTICAL, _("Practical")), 24 | (ELECTIVE, _("Elective")), 25 | (PROJECT, _("Project")), 26 | ] 27 | CP_ONE = 1 28 | CP_TEN = 20 29 | CP_CHOICE = [(i, _(str(i))) for i in range(CP_ONE, CP_TEN + 1)] 30 | # ---------------------------------------------- 31 | slug = AutoSlugField( 32 | populate_from=[ 33 | "title", 34 | "semester__sem_no", 35 | "subject_type", 36 | "credit_points", 37 | "created_by__user__first_name", 38 | "created_by__user__last_name", 39 | ] 40 | ) 41 | subject_code = models.CharField(max_length=20) 42 | title = models.CharField(max_length=200) 43 | subject_type = models.CharField( 44 | max_length=5, choices=SUBJECT_TYPE_CHOICE, default=THEORY 45 | ) 46 | credit_points = models.PositiveSmallIntegerField(choices=CP_CHOICE, default=CP_ONE) 47 | semester = models.ForeignKey( 48 | "classroom.Semester", on_delete=models.CASCADE, related_name="subjects" 49 | ) 50 | created_at = models.DateField(auto_now_add=True) 51 | created_by = models.ForeignKey( 52 | "classroom.Teacher", on_delete=models.CASCADE, related_name="subjects" 53 | ) 54 | 55 | class Meta: 56 | ordering = ["subject_code", "title", "-subject_type"] 57 | 58 | def __str__(self) -> str: 59 | return f"{self.title} - {self.subject_code}" 60 | -------------------------------------------------------------------------------- /classroom/models/teacher.py: -------------------------------------------------------------------------------- 1 | from .imports import * 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class Teacher(models.Model): 6 | 7 | user = models.OneToOneField( 8 | User, 9 | on_delete=models.CASCADE, 10 | # null=True, 11 | # blank=True, 12 | related_name="teacher_profile", 13 | ) 14 | college = models.ForeignKey( 15 | "classroom.College", on_delete=models.CASCADE, related_name="teachers" 16 | ) 17 | 18 | class Meta: 19 | ordering = ["user__first_name", "user__last_name"] 20 | 21 | def __str__(self) -> str: 22 | return f"{self.user.first_name} {self.user.last_name} | {self.user.email} " 23 | 24 | @admin.display(ordering=["user__email"]) 25 | def get_email(self): 26 | return self.user.email 27 | 28 | @admin.display(ordering=["user__first_name"]) 29 | def get_first_name(self): 30 | return self.user.first_name 31 | 32 | @admin.display(ordering=["user__last_name"]) 33 | def get_last_name(self): 34 | return self.user.last_name 35 | -------------------------------------------------------------------------------- /classroom/routers/dba_urls.py: -------------------------------------------------------------------------------- 1 | from rest_framework_nested.routers import DefaultRouter, NestedDefaultRouter 2 | from classroom.views.college_dba_view import ( 3 | AddOrDeleteOtherDBAViewSet, 4 | AllDbaProfiles, 5 | AllowedStudentManagementClassroomLevel, 6 | CollegeCreateViewSet, 7 | CollegeRetrieveForDBAViewSet, 8 | DBAProfileViewSet, 9 | ManageClassroomByDBAViewSet, 10 | StreamManagementViewSet, 11 | TeacherManagementClassroomLevel, 12 | TeacherManagementCollegeLevel, 13 | ) 14 | from termcolor import cprint 15 | 16 | college_create_router = DefaultRouter() 17 | college_create_router.register( 18 | "college-create", CollegeCreateViewSet, basename="college" 19 | ) 20 | 21 | all_dbas_router = NestedDefaultRouter( 22 | college_create_router, "college-create", lookup="college" 23 | ) 24 | all_dbas_router.register("dbas", AllDbaProfiles, basename="dbas") 25 | 26 | dba_root_router = DefaultRouter() 27 | dba_root_router.register("dba", DBAProfileViewSet, basename="dba") 28 | 29 | college_for_dba_router = DefaultRouter() 30 | college_for_dba_router.register( 31 | "college-dba", CollegeRetrieveForDBAViewSet, basename="college" 32 | ) 33 | 34 | college_for_stream = DefaultRouter() 35 | college_for_stream.register( 36 | "college-streams", CollegeRetrieveForDBAViewSet, basename="college" 37 | ) 38 | 39 | stream_router = NestedDefaultRouter( 40 | college_for_stream, "college-streams", lookup="college" 41 | ) 42 | stream_router.register("stream", StreamManagementViewSet, basename="stream") 43 | 44 | create_other_dba_router = NestedDefaultRouter( 45 | college_for_dba_router, "college-dba", lookup="college" 46 | ) 47 | create_other_dba_router.register( 48 | "manage-dba", AddOrDeleteOtherDBAViewSet, basename="manage_dba" 49 | ) 50 | 51 | classroom_create_router = NestedDefaultRouter( 52 | college_for_dba_router, "college-dba", lookup="college" 53 | ) 54 | classroom_create_router.register( 55 | "classroom", ManageClassroomByDBAViewSet, basename="classroom" 56 | ) 57 | 58 | allowed_teacher_college_router = NestedDefaultRouter( 59 | college_for_dba_router, "college-dba", lookup="college" 60 | ) 61 | allowed_teacher_college_router.register( 62 | "manage-teacher-college", TeacherManagementCollegeLevel, basename="teacher" 63 | ) 64 | 65 | allowed_teacher_classlevel_router = NestedDefaultRouter( 66 | classroom_create_router, "classroom", lookup="classroom" 67 | ) 68 | allowed_teacher_classlevel_router.register( 69 | "manage-teacher", 70 | TeacherManagementClassroomLevel, 71 | basename="teacher_classroom", 72 | ) 73 | allowed_student_classlevel_router = NestedDefaultRouter( 74 | classroom_create_router, "classroom", lookup="classroom" 75 | ) 76 | allowed_student_classlevel_router.register( 77 | "manage-student", 78 | AllowedStudentManagementClassroomLevel, 79 | basename="student_classroom", 80 | ) 81 | 82 | 83 | dba_urlpatterns = [] 84 | dba_urlpatterns += ( 85 | college_create_router.urls 86 | + stream_router.urls 87 | + dba_root_router.urls 88 | + college_for_dba_router.urls 89 | + create_other_dba_router.urls 90 | + classroom_create_router.urls 91 | + allowed_teacher_college_router.urls 92 | + allowed_teacher_classlevel_router.urls 93 | + allowed_student_classlevel_router.urls 94 | + all_dbas_router.urls 95 | ) 96 | 97 | # cprint("-------------------------------------------", "green") 98 | # cprint("DBA URLs -", "green") 99 | # cprint("-------------------------------------------", "green") 100 | # for url in dba_urlpatterns: 101 | # cprint(url, "green") 102 | -------------------------------------------------------------------------------- /classroom/routers/students_urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework_nested.routers import DefaultRouter, NestedDefaultRouter 3 | from termcolor import cprint 4 | 5 | from ..views.student_view import ( 6 | AnnouncementForStudentsViewSet, 7 | AssignmentSubmissionByStudentViewSet, 8 | AssignmentViewForStudentViewSet, 9 | ClassroomForStudentViewSet, 10 | NotesForStudentViewSet, 11 | SemesterForStudentViewSet, 12 | StudentProfileViewSet, 13 | SubjectsForStudentsViewSet, 14 | ) 15 | 16 | student_router = DefaultRouter() 17 | student_router.register("student", StudentProfileViewSet, "student") 18 | 19 | 20 | classroom_router = DefaultRouter() 21 | classroom_router.register("classroom", ClassroomForStudentViewSet, "classroom") 22 | 23 | classroom_sems_router = NestedDefaultRouter( 24 | classroom_router, "classroom", lookup="classroom" 25 | ) 26 | classroom_sems_router.register("semester", SemesterForStudentViewSet, basename="sem") 27 | 28 | sem_subjects_router = NestedDefaultRouter( 29 | classroom_sems_router, "semester", lookup="semester" 30 | ) 31 | sem_subjects_router.register("subject", SubjectsForStudentsViewSet, basename="subject") 32 | 33 | 34 | subject_announcement_router = NestedDefaultRouter( 35 | sem_subjects_router, "subject", lookup="subject" 36 | ) 37 | subject_announcement_router.register( 38 | "announcement", AnnouncementForStudentsViewSet, basename="announcement" 39 | ) 40 | 41 | subject_notes_router = NestedDefaultRouter( 42 | sem_subjects_router, "subject", lookup="subject" 43 | ) 44 | subject_notes_router.register("notes", NotesForStudentViewSet, basename="notes") 45 | 46 | subject_assignment_router = NestedDefaultRouter( 47 | sem_subjects_router, "subject", lookup="subject" 48 | ) 49 | subject_assignment_router.register( 50 | "assignment", AssignmentViewForStudentViewSet, basename="assignment" 51 | ) 52 | 53 | assignment_submission_router = NestedDefaultRouter( 54 | subject_assignment_router, "assignment", lookup="assignment" 55 | ) 56 | assignment_submission_router.register( 57 | "submission", AssignmentSubmissionByStudentViewSet, basename="submission" 58 | ) 59 | 60 | stud_urlpatterns = [] 61 | 62 | stud_urlpatterns += ( 63 | student_router.urls 64 | + classroom_router.urls 65 | + classroom_sems_router.urls 66 | + sem_subjects_router.urls 67 | + subject_announcement_router.urls 68 | + subject_notes_router.urls 69 | + subject_assignment_router.urls 70 | + assignment_submission_router.urls 71 | ) 72 | 73 | # cprint("-------------------------------------------", "green") 74 | # cprint("Student URLs -", "green") 75 | # cprint("-------------------------------------------", "green") 76 | # for url in stud_urlpatterns: 77 | # cprint(url, "cyan") 78 | -------------------------------------------------------------------------------- /classroom/routers/teacher_urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework_nested.routers import DefaultRouter, NestedDefaultRouter 3 | from termcolor import cprint 4 | 5 | from classroom.views.teacher_view import ( 6 | AnnouncementPostByTeacherViewSet, 7 | AssignmentEvaluationViewSet, 8 | AssignmentPostViewSet, 9 | ClassroomsForTeacherViewSet, 10 | FileUploadDeleteViewSet, 11 | SemesterForTeacherViewSet, 12 | SubjectForTeacherViewSet, 13 | TeacherNotesUploadViewSet, 14 | TeacherProfileViewSet, 15 | TeacherProfilesForDBAViewSet, 16 | ) 17 | from .dba_urls import college_create_router 18 | 19 | teacher_router = DefaultRouter() 20 | teacher_router.register("teacher", TeacherProfileViewSet, basename="teacher") 21 | 22 | all_teacher_profiles_for_dba_router = NestedDefaultRouter( 23 | college_create_router, "college-create", lookup="college" 24 | ) 25 | all_teacher_profiles_for_dba_router.register( 26 | "teacher-profiles", TeacherProfilesForDBAViewSet, basename="teachers" 27 | ) 28 | 29 | teacher_classrooms = NestedDefaultRouter(teacher_router, "teacher", lookup="teacher") 30 | teacher_classrooms.register( 31 | "teacher-classrooms", ClassroomsForTeacherViewSet, basename="teacher-classrooms" 32 | ) 33 | 34 | teacher_sem = NestedDefaultRouter(teacher_router, "teacher", lookup="teacher") 35 | teacher_sem.register("sem", SemesterForTeacherViewSet, basename="sem") 36 | # TODO: implement announcement routers 37 | 38 | teacher_subject = NestedDefaultRouter(teacher_sem, "sem", lookup="sem") 39 | teacher_subject.register("subject", SubjectForTeacherViewSet, basename="subject") 40 | 41 | teacher_subject_sub_urls = NestedDefaultRouter( 42 | teacher_router, "teacher", lookup="teacher" 43 | ) 44 | teacher_subject_sub_urls.register( 45 | "subject", SubjectForTeacherViewSet, basename="subject" 46 | ) 47 | 48 | teacher_subject_announcement = NestedDefaultRouter( 49 | teacher_subject_sub_urls, "subject", lookup="subject" 50 | ) 51 | teacher_subject_announcement.register( 52 | "announcement", AnnouncementPostByTeacherViewSet, basename="announcement" 53 | ) 54 | 55 | teacher_subject_notes = NestedDefaultRouter( 56 | teacher_subject_sub_urls, "subject", lookup="subject" 57 | ) 58 | teacher_subject_notes.register("notes", TeacherNotesUploadViewSet, basename="notes") 59 | 60 | teacher_notes_file_upload = NestedDefaultRouter( 61 | teacher_subject_notes, "notes", lookup="notes" 62 | ) 63 | teacher_notes_file_upload.register( 64 | "notes-files", FileUploadDeleteViewSet, basename="notes_files" 65 | ) 66 | teacher_assignment_router = NestedDefaultRouter( 67 | teacher_subject_sub_urls, "subject", lookup="subject" 68 | ) 69 | teacher_assignment_router.register( 70 | "assignment", AssignmentPostViewSet, basename="assignment" 71 | ) 72 | 73 | teacher_assignment_evaluation_router = NestedDefaultRouter( 74 | teacher_assignment_router, "assignment", lookup="assignment" 75 | ) 76 | teacher_assignment_evaluation_router.register( 77 | "submission", AssignmentEvaluationViewSet, basename="submission" 78 | ) 79 | 80 | teacher_urlpatterns = [] 81 | teacher_urlpatterns += ( 82 | teacher_router.urls 83 | + teacher_classrooms.urls 84 | + teacher_sem.urls 85 | + teacher_subject.urls 86 | + teacher_subject_announcement.urls 87 | + teacher_subject_notes.urls 88 | + teacher_notes_file_upload.urls 89 | + teacher_assignment_router.urls 90 | + teacher_assignment_evaluation_router.urls 91 | + all_teacher_profiles_for_dba_router.urls 92 | ) 93 | 94 | # cprint("-------------------------------------------", "red") 95 | # cprint("Teacher URLs -", "red") 96 | # cprint("-------------------------------------------", "red") 97 | # for url in teacher_urlpatterns: 98 | # cprint(url, "red") 99 | -------------------------------------------------------------------------------- /classroom/serializers/__init__.py: -------------------------------------------------------------------------------- 1 | from . import * 2 | -------------------------------------------------------------------------------- /classroom/serializers/student.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import ModelSerializer as ms, FileField 2 | from classroom.model import Student, User 3 | from classroom.models.assignment import Assignment, AssignmentSubmission 4 | from .classroom import ClassroomReadForStudentSerializer 5 | from accounts.serializers import CurrentUserSerializer 6 | from rest_framework.exceptions import ValidationError 7 | from rest_framework import status as code 8 | 9 | 10 | class StudentUserReadSerializer(ms): 11 | class Meta: 12 | model = User 13 | fields = ("first_name", "last_name", "contact_no", "email") 14 | 15 | 16 | class StudentReadSerializer(ms): 17 | """ 18 | Returns Student ID & User Profile along with Classroom Details 19 | """ 20 | 21 | user = StudentUserReadSerializer() 22 | classroom = ClassroomReadForStudentSerializer() 23 | 24 | class Meta: 25 | model = Student 26 | fields = ("university_roll", "user", "classroom") 27 | # read_only_fields = "__all__" 28 | # depth = 1 29 | 30 | 31 | # ---------------assignment read by student -------------------- 32 | class AssignmentReadByStudentSerializer(ms): 33 | """ 34 | # Student can only view assignment and download the file 35 | """ 36 | 37 | class Meta: 38 | model = Assignment 39 | fields = ( 40 | "id", 41 | "title", 42 | "description", 43 | "alloted_marks", 44 | "attached_pdf", 45 | "due_date", 46 | "due_time", 47 | "created_at", 48 | ) 49 | 50 | 51 | # ---------------Assignment Submission Serializers --------------- 52 | class AssignmentSubmissionReadByStudent(ms): 53 | submitted_file = FileField(max_length=None, use_url=True, required=False) 54 | 55 | class Meta: 56 | model = AssignmentSubmission 57 | fields = ( 58 | "id", 59 | "answer_section", 60 | "submitted_file", 61 | "is_submitted", 62 | "submission_date", 63 | "submission_time", 64 | "score", 65 | "has_scored", 66 | "remarks", 67 | "assignment", 68 | # "submitted_by", 69 | # "scored_by", 70 | ) 71 | read_only_fields = [ 72 | "id", 73 | "submission_date", 74 | "submission_time", 75 | "score", 76 | "has_scored", 77 | "remarks", 78 | "assignment", 79 | ] 80 | 81 | 82 | class AssignmentSubmissionWriteByStudent(ms): 83 | submitted_file = FileField(max_length=None, use_url=True, required=False) 84 | 85 | class Meta: 86 | model = AssignmentSubmission 87 | fields = ( 88 | "id", 89 | "answer_section", 90 | "submitted_file", 91 | "is_submitted", 92 | "submission_date", 93 | "submission_time", 94 | "has_scored", 95 | "submitted_by", 96 | # "score", 97 | # "assignment", 98 | # "remarks", 99 | # "scored_by", 100 | ) 101 | read_only_fields = [ 102 | "id", 103 | "submission_date", 104 | "submission_time", 105 | "has_scored", 106 | "submitted_by", 107 | ] 108 | 109 | def create(self, validated_data): 110 | assignment_pk = self.context.get("assignment_pk") 111 | submitted_by = self.context.get("user_id") 112 | try: 113 | assignment = Assignment.objects.get(id=assignment_pk) 114 | except: 115 | raise ValidationError("Assignment Not Found", code=code.HTTP_404_NOT_FOUND) 116 | try: 117 | student: Student = Student.objects.select_related("user").get( 118 | user__id=submitted_by 119 | ) 120 | except: 121 | raise ValidationError( 122 | "Student Profile Not Found", code=code.HTTP_404_NOT_FOUND 123 | ) 124 | if AssignmentSubmission.objects.filter( 125 | assignment=assignment, submitted_by=student 126 | ).exists(): 127 | raise ValidationError( 128 | "1 Submission Allowed Per Student", code=code.HTTP_400_BAD_REQUEST 129 | ) 130 | 131 | try: 132 | self.instance = AssignmentSubmission.objects.create( 133 | assignment=assignment, submitted_by=student, **validated_data 134 | ) 135 | except: 136 | raise ValidationError( 137 | "Assignment Submission Failed", code=code.HTTP_304_NOT_MODIFIED 138 | ) 139 | return self.instance 140 | -------------------------------------------------------------------------------- /classroom/serializers/teacher.py: -------------------------------------------------------------------------------- 1 | from rest_framework.serializers import ModelSerializer as ms, IntegerField, FileField 2 | from accounts.serializers import CurrentUserSerializer 3 | from classroom.model import Classroom, Teacher, User 4 | 5 | 6 | class MinimalUserDetailsSerializer(ms): 7 | class Meta: 8 | model = User 9 | fields = ["first_name", "last_name", "email", "contact_no"] 10 | 11 | 12 | class MinimalTeacherDetailsSerializer(ms): 13 | user = MinimalUserDetailsSerializer() 14 | teacher_id = IntegerField(source="id") 15 | 16 | class Meta: 17 | model = Teacher 18 | fields = ["user", "teacher_id"] 19 | 20 | 21 | class TeacherReadForSubjectSerializer(ms): 22 | user = MinimalUserDetailsSerializer() 23 | 24 | class Meta: 25 | model = Teacher 26 | fields = "__all__" 27 | depth = 1 28 | select_related_fields = ["user"] 29 | 30 | 31 | class TeacherClassroomsGetSerializer(ms): 32 | class Meta: 33 | model = Classroom 34 | fields = ( 35 | "slug", 36 | "title", 37 | "level", 38 | "stream", 39 | "start_year", 40 | "end_year", 41 | "section", 42 | "no_of_semesters", 43 | "current_sem", 44 | ) 45 | 46 | 47 | # ----------------- teacher view serializers ----------------- 48 | class TeacherProfileSerializer(ms): 49 | user = MinimalUserDetailsSerializer() 50 | teacher_id = IntegerField(source="id") 51 | classroom_list = TeacherClassroomsGetSerializer(many=True, source="classrooms") 52 | 53 | class Meta: 54 | model = Teacher 55 | fields = ["teacher_id", "user", "classroom_list"] 56 | 57 | 58 | class TeacherProfileForDBASerializer(ms): 59 | user = MinimalUserDetailsSerializer() 60 | teacher_id = IntegerField(source="id") 61 | # classroom_list = TeacherClassroomsGetSerializer(many=True, source="classrooms") 62 | 63 | class Meta: 64 | model = Teacher 65 | fields = ["teacher_id", "user"] 66 | -------------------------------------------------------------------------------- /classroom/serializers/usertype.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | 4 | class UserTypeSerializer(serializers.Serializer): 5 | # user_type = serializers.CharField(trim_whitespace=True) 6 | user_type = serializers.ChoiceField( 7 | choices=[ 8 | ("student", "student"), 9 | ("teacher", "teacher"), 10 | ("college_dba", "college_dba"), 11 | ] 12 | ) 13 | usertype_id = serializers.IntegerField() 14 | -------------------------------------------------------------------------------- /classroom/signals/__init__.py: -------------------------------------------------------------------------------- 1 | from django.dispatch import Signal 2 | 3 | # classroom_updated = Signal() #FIXME: Not working 4 | -------------------------------------------------------------------------------- /classroom/signals/common_imports.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pandas as pd 4 | from celery import shared_task 5 | from classroom.model import ( 6 | AllowedStudents, 7 | AllowedTeacher, 8 | AllowedTeacherClassroomLevel, 9 | Classroom, 10 | College, 11 | Semester, 12 | Student, 13 | Teacher, 14 | User, 15 | ) 16 | from classroom.models.college import AllowedCollegeDBA 17 | from classroom.models.college_dba import CollegeDBA 18 | from classroom.serializers import teacher 19 | from classroom.tasks import send_email_after_bulk_object_creation 20 | from django.conf import settings 21 | from django.core.mail import send_mail 22 | from django.db.models.signals import post_delete, post_save, pre_save 23 | from django.dispatch import receiver 24 | from django.http import BadHeaderError 25 | from rest_framework.exceptions import ValidationError 26 | from rest_framework import status 27 | from rest_framework.response import Response 28 | from termcolor import cprint 29 | from django.db.transaction import atomic 30 | 31 | 32 | def delete_college_on_any_failure(id): 33 | try: 34 | College.objects.filter(pk=id).delete() 35 | cprint(f"College Deleted with ID:[{id}]") 36 | except: 37 | cprint(f"No College Found with ID:[{id}]") 38 | -------------------------------------------------------------------------------- /classroom/signals/dba_handlers.py: -------------------------------------------------------------------------------- 1 | from .common_imports import * 2 | from rest_framework import status 3 | 4 | 5 | @shared_task 6 | @receiver(post_save, sender=College) 7 | def create_allowed_dba(sender, instance: College, created, **kwargs): 8 | if created: 9 | is_owner_of_college_exists = AllowedCollegeDBA.objects.filter( 10 | email=instance.owner_email_id 11 | ).exists() 12 | if is_owner_of_college_exists: 13 | delete_college_on_any_failure(instance.id) 14 | raise ValidationError( 15 | detail=f""" 16 | college owner {instance.owner_email_id} already associated with 17 | college - {instance.name}""", 18 | code=status.HTTP_400_BAD_REQUEST, 19 | ) 20 | else: 21 | AllowedCollegeDBA.objects.create( 22 | college=instance, email=instance.owner_email_id 23 | ) 24 | subject = f"Welcome to {instance.name}" 25 | body = f""" 26 | You are the owner admin of college {instance.name} 27 | Now you can sign up with mail id - {instance.owner_email_id} 28 | ---------------- 29 | NB: Only you will be able to add other DBAs or remove them 30 | """ 31 | send_mail( 32 | subject, body, settings.EMAIL_HOST_USER, [instance.owner_email_id] 33 | ) 34 | if instance.allowed_dba_list == None: 35 | delete_college_on_any_failure(instance.id) 36 | raise ValidationError( 37 | detail="Allowed Admin List Not Found,College Creation Failed", 38 | code=status.HTTP_404_NOT_FOUND, 39 | ) 40 | file_abs_path = None 41 | dba_file_path = os.path.join( 42 | settings.BASE_DIR, 43 | settings.MEDIA_ROOT, 44 | instance.allowed_dba_list.name, 45 | ) 46 | if os.path.exists(dba_file_path): 47 | file_abs_path = os.path.abspath(dba_file_path) 48 | else: 49 | delete_college_on_any_failure(instance.id) 50 | raise ValidationError( 51 | detail="Stream List Not Found,College Creation Failed", 52 | code=status.HTTP_404_NOT_FOUND, 53 | ) 54 | 55 | df = None 56 | if str(file_abs_path).split(".")[-1] == "csv": 57 | df: pd.DataFrame = pd.read_csv(file_abs_path) 58 | elif str(file_abs_path).split(".")[-1] == "xlsx": 59 | df = pd.read_excel(file_abs_path) 60 | else: 61 | delete_college_on_any_failure(instance.id) 62 | raise ValidationError( 63 | detail="Allowed Admin List Not of Type [Xl/CSV] ,College Creation Failed", 64 | code=status.HTTP_400_BAD_REQUEST, 65 | ) 66 | 67 | if df.shape[1] != 1: 68 | delete_college_on_any_failure(instance.id) 69 | raise ValidationError( 70 | f"{instance.allowed_dba_list.name} file should contain ONE Column namely email", 71 | code=status.HTTP_400_BAD_REQUEST, 72 | ) 73 | if not df.shape[0] > 0: 74 | delete_college_on_any_failure(instance.id) 75 | raise ValidationError( 76 | f"{instance.allowed_dba_list.name} file can not be empty", 77 | code=status.HTTP_400_BAD_REQUEST, 78 | ) 79 | 80 | if not "email" in df.columns: 81 | delete_college_on_any_failure(instance.id) 82 | raise ValidationError( 83 | f"{instance.stream_list.name} file should contain ONE Column namely email", 84 | code=status.HTTP_400_BAD_REQUEST, 85 | ) 86 | # df_dict = df.to_dict("records") 87 | # if len(df_dict) < 1: 88 | 89 | # raise ValidationError( 90 | # detail="Please give at least one mail-id in the fail " 91 | # ) 92 | 93 | # print(df_dict) 94 | try: 95 | list_of_teachers = [ 96 | AllowedCollegeDBA(college=instance, **args) 97 | for args in df.to_dict("records") 98 | ] 99 | with atomic(): 100 | AllowedCollegeDBA.objects.bulk_create(list_of_teachers) 101 | except: 102 | delete_college_on_any_failure(instance.id) 103 | raise ValidationError( 104 | f"Bulk Allowed College DBA creation failed, College Creation Failed", 105 | code=status.HTTP_400_BAD_REQUEST, 106 | ) 107 | email_list = df["email"].to_list() 108 | subject = "Open Your DBA Account" 109 | prompt = "please use your following mail id to sign up in the Classroom[LMS]" 110 | try: 111 | send_email_after_bulk_object_creation.delay(subject, prompt, email_list) 112 | except BadHeaderError: 113 | print("Could not able to send emails to DBAs") 114 | os.remove(file_abs_path) 115 | College.objects.update(allowed_dba_list="") 116 | 117 | 118 | @receiver(post_delete, sender=AllowedCollegeDBA) 119 | def delete_dba_account_on_deletion_of_allowed_cllg_dba( 120 | sender, instance: AllowedCollegeDBA, **kwargs 121 | ): 122 | try: 123 | college_dba = CollegeDBA.objects.select_related("user").filter( 124 | user__email=instance.email 125 | ) 126 | if college_dba.exists(): 127 | try: 128 | college_dba.delete() 129 | except: 130 | pass 131 | except: 132 | pass 133 | -------------------------------------------------------------------------------- /classroom/signals/handlers.py: -------------------------------------------------------------------------------- 1 | from .classroom_handlers import ( 2 | create_allowed_students, 3 | create_allowed_teacher_for_classroom_level, 4 | create_allowed_teacher_for_classroom_level_with_check, 5 | create_sems_for_new_classroom, 6 | ) 7 | from .college_handlers import ( 8 | create_allowed_teacher, 9 | remove_teacher_profile_after_allowed_teacher_deletion, 10 | send_mail_after_create_allowed_teacher, 11 | ) 12 | from .dba_handlers import create_allowed_dba 13 | from .profile_handlers import create_profile 14 | from .teacher_classroom_handlers import ( 15 | assign_classroom_to_existing_teacher, 16 | auto_join_teacher_to_classes, 17 | remove_class_after_removal_of_assigned_teacher, 18 | ) 19 | from .user_handlers import ( 20 | delete_user_on_dba_delete, 21 | delete_user_on_student_delete, 22 | delete_user_on_teacher_delete, # FIXME: not working 23 | ) 24 | -------------------------------------------------------------------------------- /classroom/signals/profile_handlers.py: -------------------------------------------------------------------------------- 1 | from classroom.models.college import Stream 2 | from .common_imports import * 3 | 4 | 5 | @receiver(post_save, sender=settings.AUTH_USER_MODEL) 6 | def create_profile(sender, instance: settings.AUTH_USER_MODEL, created, **kwargs): 7 | if created: 8 | if ( 9 | AllowedStudents.objects.filter(email=instance.email).exists() 10 | and not Student.objects.select_related("user") 11 | .filter(user=instance) 12 | .exists() 13 | ): 14 | classroom: Classroom = AllowedStudents.objects.get( 15 | email=instance.email 16 | ).classroom 17 | university_roll = AllowedStudents.objects.get( 18 | email=instance.email 19 | ).university_roll 20 | s = Student.objects.create( 21 | university_roll=university_roll, user=instance, classroom=classroom 22 | ) 23 | # TODO: Add some other info also in the mail 24 | subject = "Your Student Profile Has Been Created Successfully" 25 | msg = f""" 26 | Student ID :{s.id} 27 | mail : {instance.email} 28 | classroom : {classroom.title} 29 | 30 | You Can Login After Activation Of your account 31 | """ 32 | send_mail(subject, msg, settings.EMAIL_HOST_USER, [instance.email]) 33 | elif ( 34 | AllowedTeacher.objects.filter(email=instance.email).exists() 35 | and not Teacher.objects.select_related("user") 36 | .filter(user=instance) 37 | .exists() 38 | ): 39 | college_detail = College.objects.get( 40 | pk=( 41 | AllowedTeacher.objects.filter(email=instance.email) 42 | .select_related("college") 43 | .values_list("college", flat=True) 44 | )[0] 45 | ) 46 | from termcolor import cprint 47 | 48 | cprint(college_detail, "red") 49 | t = Teacher.objects.create(user=instance, college=college_detail) 50 | subject = "Your Teacher Profile Has Been Created Successfully" 51 | msg = f""" 52 | Teacher ID :{t.id} 53 | mail : {instance.email} 54 | 55 | You Can Login After Activation Of your account 56 | """ 57 | send_mail(subject, msg, settings.EMAIL_HOST_USER, [instance.email]) 58 | elif ( 59 | AllowedCollegeDBA.objects.filter(email=instance.email).exists() 60 | and not CollegeDBA.objects.select_related("user") 61 | .filter(user=instance) 62 | .exists() 63 | ): 64 | from termcolor import cprint 65 | 66 | # cprint("In DBA Creation", "red") 67 | college_detail: College = College.objects.get( 68 | pk=( 69 | AllowedCollegeDBA.objects.filter(email=instance.email) 70 | .select_related("college") 71 | .values_list("college", flat=True) 72 | )[0] 73 | ) 74 | 75 | # cprint(college_detail, "red") 76 | is_owner = False 77 | if instance.email == college_detail.owner_email_id: 78 | is_owner = True 79 | # cprint(f"is owner --> [ {is_owner} ]", "red") 80 | t: CollegeDBA = CollegeDBA.objects.create( 81 | user=instance, college=college_detail, is_owner=is_owner 82 | ) 83 | if is_owner: 84 | Stream.objects.select_related("college").filter( 85 | college__id=college_detail.id 86 | ).update(dba=t) 87 | subject = "Your DBA Profile Has Been Created Successfully" 88 | msg = f""" 89 | COLLEGE DBA ID :{t.id} 90 | mail : {instance.email} 91 | 92 | You Can Login After Activation Of your account 93 | """ 94 | send_mail(subject, msg, settings.EMAIL_HOST_USER, [instance.email]) 95 | elif instance.is_superuser or instance.is_staff: # ADMIN 96 | print("Admin") 97 | else: 98 | subject = "Profile Creation Failed" 99 | msg = f""" 100 | You have not been assigned any profile for any college 101 | 102 | contact mail id: {settings.EMAIL_HOST_USER} 103 | """ 104 | send_mail(subject, msg, settings.EMAIL_HOST_USER, [instance.email]) 105 | # User.objects.filter(pk=instance.id).delete() 106 | raise ValidationError( 107 | "Profile creation failed, as you have no profile attached with any college", 108 | code=status.HTTP_401_UNAUTHORIZED, 109 | ) 110 | -------------------------------------------------------------------------------- /classroom/signals/teacher_classroom_handlers.py: -------------------------------------------------------------------------------- 1 | from .common_imports import * 2 | 3 | 4 | @shared_task 5 | @receiver(post_save, sender=Teacher) 6 | def auto_join_teacher_to_classes(sender, instance: Teacher, created, **kwargs): 7 | # from termcolor import cprint 8 | 9 | if created: 10 | qset = Classroom.objects.prefetch_related("allowed_teachers").filter( 11 | allowed_teachers__email=instance.user.email 12 | ) 13 | # cprint(list(qset), "blue") 14 | try: 15 | instance.classrooms.add(*qset) 16 | except: 17 | pass 18 | # cprint("already assigned classrooms to teacher ", "yellow") 19 | 20 | 21 | @receiver( 22 | post_save, sender=AllowedTeacherClassroomLevel 23 | ) # FIXME: classroom pre signed up teachers are not saving 24 | def assign_classroom_to_existing_teacher( 25 | sender, instance: AllowedTeacherClassroomLevel, created, **kwargs 26 | ): 27 | from termcolor import cprint 28 | 29 | t = ( 30 | "assign_classroom_to_existing_teacher " 31 | + instance.email 32 | + "\n---> " 33 | + str(created) 34 | ) 35 | cprint(t, "red") 36 | if created: 37 | classroom: Classroom = Classroom.objects.select_related("college").get( 38 | pk=instance.classroom.id 39 | ) 40 | cprint(classroom, "red") 41 | teacher_query = Teacher.objects.select_related("user").filter( 42 | user__email=instance.email 43 | ) 44 | cprint(str(teacher_query.exists()) + " -> " + instance.email, "blue") 45 | if teacher_query.exists(): 46 | teacher = teacher_query.first() 47 | from django.db import transaction 48 | 49 | with transaction.atomic(): 50 | classroom.teachers.add(teacher) 51 | classroom.save(force_update=True) 52 | for tchr in classroom.teachers.all(): 53 | cprint("Classrooms of teacher -> ", "cyan") 54 | cprint(tchr, "cyan") 55 | owner_mail_id = classroom.college.owner_email_id 56 | cprint(f"owner mail id --> {owner_mail_id}", "red") 57 | subject = "Sir You have been Assigned A new Class" 58 | msg = f"Classroom - {classroom.title}" 59 | send_mail(subject, msg, owner_mail_id, [instance.email]) 60 | 61 | 62 | @shared_task 63 | @receiver(post_delete, sender=AllowedTeacherClassroomLevel) 64 | def remove_class_after_removal_of_assigned_teacher( 65 | sender, instance: AllowedTeacherClassroomLevel, **kwargs 66 | ): 67 | """ 68 | this removes the classroom from the teacher if teacher 69 | has been removed from allowed class room level 70 | """ 71 | classroom: Classroom = Classroom.objects.select_related("college").get( 72 | pk=instance.classroom.id 73 | ) 74 | teacher_query = Teacher.objects.select_related("user").filter( 75 | user__email=instance.email 76 | ) 77 | if teacher_query.exists(): 78 | teacher_query.first().classrooms.remove(classroom) 79 | subject = "Sir You have been Removed From A Class" 80 | msg = f"Classroom - {classroom.title}" 81 | owner_mail_id = classroom.college.owner_email_id 82 | cprint(f"owner mail id --> {owner_mail_id}", "red") 83 | send_mail(subject, msg, owner_mail_id, [instance.email]) 84 | -------------------------------------------------------------------------------- /classroom/signals/user_handlers.py: -------------------------------------------------------------------------------- 1 | from .common_imports import * 2 | 3 | 4 | # @shared_task 5 | @receiver(post_delete, sender=Student) 6 | def delete_user_on_student_delete(sender, instance: Student, **kwargs): 7 | try: 8 | user = User.objects.filter(pk=instance.user.id) 9 | if user.exists(): 10 | user.delete() 11 | try: 12 | subject = "You Have Been Removed from Classroom" 13 | body = f""" 14 | your mail - {instance.user.email} is no more associated with any college or classroom 15 | """ 16 | except: 17 | cprint( 18 | "student profile deletion confirmation mail sending failed", "red" 19 | ) 20 | except: 21 | pass 22 | 23 | 24 | # @shared_task 25 | @receiver(post_delete, sender=CollegeDBA) 26 | def delete_user_on_dba_delete(sender, instance: CollegeDBA, **kwargs): 27 | try: 28 | user = User.objects.filter(pk=instance.user.id) 29 | if user.exists(): 30 | user.delete() 31 | try: 32 | subject = "Your DBA Profile Have Been Removed from College" 33 | body = f""" 34 | your mail - {instance.user.email} is no more associated with any college or classroom 35 | """ 36 | except: 37 | cprint("admin profile deletion confirmation mail sending failed", "red") 38 | except: 39 | pass 40 | 41 | 42 | # @shared_task 43 | @receiver( 44 | post_delete, sender=Teacher 45 | ) # FIXME: Off this code if teacher removal from class deletes user 46 | def delete_user_on_teacher_delete(sender, instance: Teacher, **kwargs): 47 | try: 48 | if instance.user: 49 | user = User.objects.filter(pk=instance.user.id) 50 | if user.exists(): 51 | user.delete() 52 | try: 53 | subject = "Your Teacher Profile Have Been Removed from College" 54 | body = f""" 55 | your mail - {instance.user.email} is no more associated with any college or classroom 56 | """ 57 | except: 58 | cprint( 59 | "teacher profile deletion confirmation mail sending failed", 60 | "red", 61 | ) 62 | except: 63 | pass 64 | -------------------------------------------------------------------------------- /classroom/tasks.py: -------------------------------------------------------------------------------- 1 | from celery import shared_task 2 | from django.core.mail import send_mass_mail, send_mail 3 | from .model import AllowedTeacher 4 | import os 5 | from django.conf import settings 6 | import pandas as pd 7 | 8 | 9 | @shared_task 10 | def send_email_after_bulk_object_creation(subject: str, prompt: str, email_list): 11 | """ 12 | # It sends mass mail 13 | - takes 3 `parameter` 14 | - subject 15 | - common prompt 16 | - email_list 17 | """ 18 | mails = [ 19 | ( 20 | subject, 21 | f"{prompt} - \nUSED MAIL ID : {m}", 22 | settings.EMAIL_HOST_USER, 23 | [m], 24 | ) 25 | for m in email_list 26 | ] 27 | send_mass_mail(mails, fail_silently=True) 28 | -------------------------------------------------------------------------------- /classroom/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /classroom/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .routers.students_urls import stud_urlpatterns 3 | from .routers.teacher_urls import teacher_urlpatterns 4 | from .routers.dba_urls import dba_urlpatterns 5 | from classroom.views.usertype_view import UserTypeAPIView 6 | 7 | urlpatterns = [ 8 | path("user-type/", UserTypeAPIView.as_view(), name="user-category") 9 | ] 10 | urlpatterns += stud_urlpatterns + teacher_urlpatterns + dba_urlpatterns 11 | -------------------------------------------------------------------------------- /classroom/validators.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | from django.core.exceptions import ValidationError 3 | 4 | 5 | def is_no_of_sem_even(value): 6 | if value % 2 == 0: 7 | return value 8 | else: 9 | raise ValidationError("for a course number of sems have to be even") 10 | 11 | 12 | def pdf_file_size_lt_5mb(value): 13 | limit_no = 5 14 | limit = limit_no * 1024 * 1024 15 | if value.size > limit: 16 | raise ValidationError(f"File too large. Size should not exceed {limit_no} Mb.") 17 | 18 | 19 | def assignment_date_gte_today(date_val: date): 20 | if date_val < date.today(): 21 | raise ValidationError( 22 | f"Assignment due date- [{date_val:%d-%m-%Y}] can not be less than current date - {(date.today()):%d-%m-%Y}" 23 | ) 24 | return date_val 25 | -------------------------------------------------------------------------------- /classroom/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/classroom/views/__init__.py -------------------------------------------------------------------------------- /classroom/views/student_view.py: -------------------------------------------------------------------------------- 1 | from classroom.models.assignment import Assignment, AssignmentSubmission 2 | from classroom.serializers.classroom import ( 3 | AnnouncementsReadSerializer, 4 | ClassroomReadForStudentSerializer, 5 | NotesReadForStudentSerializer, 6 | SemesterReadSerializer, 7 | SubjectReadSerializer, 8 | ) 9 | from classroom.serializers.student import ( 10 | AssignmentReadByStudentSerializer, 11 | AssignmentSubmissionReadByStudent, 12 | AssignmentSubmissionWriteByStudent, 13 | StudentReadSerializer, 14 | ) 15 | from rest_framework.mixins import ( 16 | ListModelMixin, 17 | RetrieveModelMixin, 18 | UpdateModelMixin, 19 | CreateModelMixin, 20 | DestroyModelMixin, 21 | ) 22 | from rest_framework.viewsets import GenericViewSet, ModelViewSet 23 | from rest_framework.parsers import FormParser, MultiPartParser 24 | from ..model import Announcement, Classroom, Notes, Semester, Student, Subject 25 | 26 | 27 | class StudentProfileViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): 28 | """ 29 | Student End point will return student profile details along with classroom details 30 | """ 31 | 32 | my_tags = ["[student] 1. profile"] 33 | queryset = ( 34 | Student.objects.select_related("user") 35 | .select_related("classroom") 36 | .select_related("classroom__college") 37 | .all() 38 | ) 39 | serializer_class = StudentReadSerializer 40 | 41 | def get_serializer_context(self): 42 | return {"request": self.request} 43 | 44 | 45 | class ClassroomForStudentViewSet(RetrieveModelMixin, GenericViewSet): 46 | """ 47 | This view is used by student only 48 | student can only retrieve but won't be able to see the other classrooms 49 | """ 50 | 51 | my_tags = ["[student] -. classroom"] 52 | swagger_schema = None 53 | serializer_class = ClassroomReadForStudentSerializer 54 | lookup_field = "slug" 55 | 56 | def get_queryset(self): 57 | classroom = ( 58 | Classroom.objects.select_related("college") 59 | .prefetch_related("students") 60 | .filter(slug=self.kwargs.get("slug")) 61 | ) 62 | return classroom 63 | 64 | 65 | class SemesterForStudentViewSet( 66 | ListModelMixin, RetrieveModelMixin, UpdateModelMixin, GenericViewSet 67 | ): 68 | http_method_names = ["get", "patch", "head", "options"] 69 | my_tags = ["[student] 2. semester"] 70 | serializer_class = SemesterReadSerializer 71 | # lookup_field = 'id' 72 | def get_queryset(self): 73 | sem = Semester.objects.select_related("classroom").filter( 74 | classroom__slug=self.kwargs.get("classroom_slug") 75 | ) 76 | return sem 77 | 78 | 79 | class SubjectsForStudentsViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): 80 | my_tags = ["[student] 3. subjects"] 81 | serializer_class = SubjectReadSerializer 82 | lookup_field = "slug" 83 | 84 | def get_queryset(self): 85 | return ( 86 | Subject.objects.select_related("semester") 87 | .select_related("semester__classroom") 88 | .filter(semester__id=self.kwargs.get("semester_pk")) 89 | ) # FIXME: This might be slow in future. Req: Optimization 90 | 91 | 92 | class AnnouncementForStudentsViewSet(ListModelMixin, GenericViewSet): 93 | """ 94 | Announcements for the particular subject will be shown in decreasing order 95 | """ 96 | 97 | my_tags = ["[student] 4. announcements/subject "] 98 | serializer_class = AnnouncementsReadSerializer 99 | 100 | def get_queryset(self): 101 | return Announcement.objects.select_related("subject").filter( 102 | subject__slug=self.kwargs.get("subject_slug") 103 | ) 104 | 105 | 106 | class NotesForStudentViewSet(ListModelMixin, RetrieveModelMixin, GenericViewSet): 107 | """ 108 | Notes for the particular subject will be shown in decreasing order 109 | """ 110 | 111 | my_tags = ["[student] 5. notes/subject"] 112 | serializer_class = NotesReadForStudentSerializer 113 | lookup_field = "slug" 114 | 115 | def get_queryset(self): 116 | return ( 117 | Notes.objects.select_related("subject", "posted_by", "posted_by__user") 118 | .prefetch_related("attached_files") 119 | .filter(subject__slug=self.kwargs.get("subject_slug")) 120 | ) 121 | 122 | 123 | # TODO: Add Assignment View 124 | class AssignmentViewForStudentViewSet( 125 | ListModelMixin, RetrieveModelMixin, GenericViewSet 126 | ): 127 | my_tags = ["[student] 6. assignments/subject"] 128 | serializer_class = AssignmentReadByStudentSerializer 129 | # parser_classes = [FormParser,MultiPartParser] 130 | 131 | def get_queryset(self): 132 | return Assignment.objects.select_related("subject").filter( 133 | subject__slug=self.kwargs.get("subject_slug") 134 | ) 135 | 136 | 137 | class AssignmentSubmissionByStudentViewSet(ModelViewSet): 138 | http_method_names = ["get", "post", "delete", "head", "options"] 139 | my_tags = ["[student] 7. assignment submission"] 140 | serializer_class = AssignmentSubmissionWriteByStudent 141 | parser_classes = [ 142 | FormParser, 143 | MultiPartParser, 144 | ] 145 | 146 | def get_queryset(self): 147 | # TODO:Dynamically find student id before submission 148 | student: Student = Student.objects.select_related("user").get( 149 | user__id=self.request.user.id 150 | ) 151 | return AssignmentSubmission.objects.filter( 152 | assignment__id=self.kwargs.get("assignment_pk"), submitted_by=student.id 153 | ) 154 | 155 | def get_serializer_context(self): 156 | return { 157 | "assignment_pk": self.kwargs.get("assignment_pk"), 158 | "user_id": self.request.user.id, 159 | } 160 | 161 | def get_serializer_class(self): 162 | method = self.request.method 163 | if method == "POST": 164 | return AssignmentSubmissionWriteByStudent 165 | return AssignmentSubmissionReadByStudent 166 | -------------------------------------------------------------------------------- /classroom/views/usertype_view.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import RetrieveAPIView as _rav 2 | from rest_framework import status 3 | from classroom.models.college_dba import CollegeDBA 4 | from classroom.models.student import Student 5 | 6 | from classroom.models.teacher import Teacher 7 | from rest_framework.response import Response 8 | 9 | from classroom.serializers.usertype import UserTypeSerializer 10 | 11 | # hello 12 | class UserTypeAPIView(_rav): 13 | my_tags = ["auth [user category]"] 14 | 15 | def get_serializer_class(self): 16 | return UserTypeSerializer 17 | 18 | def get(self, request, id, **kwargs): 19 | if Teacher.objects.select_related("user").filter(user__id=id).exists(): 20 | teacher = ( 21 | Teacher.objects.select_related("user").filter(user__id=id) 22 | # .only("id") 23 | .first() 24 | ) 25 | return Response( 26 | {"user_type": "teacher", "teacher_id": teacher.id}, 27 | status=status.HTTP_200_OK, 28 | ) 29 | 30 | elif Student.objects.select_related("user").filter(user__id=id).exists(): 31 | student = ( 32 | Student.objects.select_related("user").filter(user__id=id) 33 | # .only("id") 34 | .first() 35 | ) 36 | return Response( 37 | {"user_type": "student", "student_id": student.id}, 38 | status=status.HTTP_200_OK, 39 | ) 40 | 41 | elif CollegeDBA.objects.select_related("user").filter(user__id=id).exists(): 42 | 43 | dba = ( 44 | CollegeDBA.objects.select_related("user").filter(user__id=id) 45 | # .only("id") 46 | .first() 47 | ) 48 | return Response( 49 | {"user_type": "college_dba", "dba_id": dba.id}, 50 | status=status.HTTP_200_OK, 51 | ) 52 | 53 | else: 54 | 55 | return Response( 56 | {"error": {"user_type": "user unknown"}}, 57 | status=status.HTTP_404_NOT_FOUND, 58 | ) 59 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | from .celery import celery 2 | -------------------------------------------------------------------------------- /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/4.0/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.dev") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /core/celery.py: -------------------------------------------------------------------------------- 1 | import os 2 | from celery import Celery 3 | import celery 4 | 5 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings.dev") 6 | 7 | celery = Celery("core") 8 | celery.config_from_object("django.conf:settings", namespace="CELERY") 9 | celery.autodiscover_tasks() 10 | -------------------------------------------------------------------------------- /core/mail.py: -------------------------------------------------------------------------------- 1 | class EmailCreds: 2 | def __init__(self) -> None: 3 | self.__mail_id__ = "django.dev.tmsl@gmail.com" 4 | self.EMAIL_HOST = "smtp.gmail.com" 5 | self.EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" 6 | self.PORT = 587 7 | 8 | def get_mail_id(self) -> str: 9 | return self.__mail_id__ 10 | -------------------------------------------------------------------------------- /core/settings/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for core project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.0.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.0/ref/settings/ 11 | """ 12 | import os 13 | from ..mail import EmailCreds 14 | from datetime import timedelta 15 | from pathlib import Path 16 | 17 | EMAIL_CREDS = EmailCreds() 18 | 19 | 20 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 21 | BASE_DIR = Path(__file__).resolve().parent.parent.parent # FIXME: .parent added 22 | 23 | # Application definition 24 | 25 | INSTALLED_APPS = [ 26 | "django.contrib.admin", 27 | "django.contrib.auth", 28 | "django.contrib.contenttypes", 29 | "django.contrib.sessions", 30 | "django.contrib.messages", 31 | "django.contrib.staticfiles", 32 | ] 33 | THIRD_PARTY_APP = [ 34 | "corsheaders", 35 | "rest_framework", 36 | "djoser", 37 | "django_extensions", 38 | "drf_yasg", 39 | ] 40 | 41 | OWN_APP = ["accounts", "classroom"] 42 | 43 | INSTALLED_APPS += THIRD_PARTY_APP + OWN_APP 44 | 45 | MIDDLEWARE = [ 46 | "django.middleware.security.SecurityMiddleware", 47 | "whitenoise.middleware.WhiteNoiseMiddleware", # TODO: 3rd party middleware 48 | "django.contrib.sessions.middleware.SessionMiddleware", 49 | "django.middleware.common.CommonMiddleware", 50 | # "django.middleware.csrf.CsrfViewMiddleware", 51 | "django.contrib.auth.middleware.AuthenticationMiddleware", 52 | "django.contrib.messages.middleware.MessageMiddleware", 53 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 54 | ] 55 | 56 | 57 | THIRD_PARTY_MIDDLEWARE = [ 58 | "corsheaders.middleware.CorsMiddleware", 59 | ] 60 | 61 | MIDDLEWARE = THIRD_PARTY_MIDDLEWARE + MIDDLEWARE 62 | 63 | 64 | ROOT_URLCONF = "core.urls" 65 | 66 | TEMPLATES = [ 67 | { 68 | "BACKEND": "django.template.backends.django.DjangoTemplates", 69 | "DIRS": [], 70 | "APP_DIRS": True, 71 | "OPTIONS": { 72 | "context_processors": [ 73 | "django.template.context_processors.debug", 74 | "django.template.context_processors.request", 75 | "django.contrib.auth.context_processors.auth", 76 | "django.contrib.messages.context_processors.messages", 77 | ], 78 | }, 79 | }, 80 | ] 81 | 82 | WSGI_APPLICATION = "core.wsgi.application" 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 91 | }, 92 | { 93 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 94 | }, 95 | { 96 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 97 | }, 98 | { 99 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/4.0/topics/i18n/ 106 | 107 | LANGUAGE_CODE = "en-us" 108 | 109 | TIME_ZONE = "Asia/Kolkata" 110 | # TIME_ZONE = "UTC" 111 | 112 | USE_I18N = True 113 | 114 | # USE_L10N = True 115 | 116 | USE_TZ = True 117 | 118 | 119 | # Static files (CSS, JavaScript, Images) 120 | # https://docs.djangoproject.com/en/4.0/howto/static-files/ 121 | 122 | STATIC_URL = "static/" 123 | STATIC_ROOT = os.path.join( 124 | BASE_DIR, "static" 125 | ) # This line will collect all static files 126 | 127 | MEDIA_URL = "media/" 128 | MEDIA_ROOT = os.path.join(BASE_DIR, "media") 129 | # STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" # FIXME: if not works, delete these line 130 | 131 | # Default primary key field type 132 | # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field 133 | 134 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 135 | 136 | # -----------------------------#3-RD PARTY APPS CONFIG ------------------- 137 | 138 | 139 | # TODO: Djoser Settings Open 140 | DJOSER = { 141 | # "SET_PASSWORD_RETYPE": True, #TODO: Confirm Password [Djoser] 142 | "SEND_CONFIRMATION_EMAIL": True, 143 | "SEND_ACTIVATION_EMAIL": True, 144 | "ACTIVATION_URL": "activate/{uid}/{token}", 145 | "PASSWORD_CHANGED_EMAIL_CONFIRMATION": True, 146 | "PASSWORD_RESET_CONFIRM_URL": "reset_password_confirm/{uid}/{token}", 147 | "USERNAME_CHANGED_EMAIL_CONFIRMATION": True, 148 | "USERNAME_RESET_CONFIRM_URL": "reset_email_confirm/{uid}/{token}", 149 | # "PASSWORD_RESET_SHOW_EMAIL_NOT_FOUND": False, #TODO: if want to expose details about email then open it else leave it 150 | "SERIALIZERS": { 151 | "current_user": "accounts.serializers.CurrentUserSerializer", 152 | "username_reset_confirm": "accounts.serializers.UseranmeResetConfirmSerializer", 153 | }, 154 | } 155 | 156 | SWAGGER_SETTINGS = { 157 | "DEFAULT_AUTO_SCHEMA_CLASS": "core.swagger_schema.CustomAutoSchema", 158 | "LOGIN_URL": "admin/", 159 | "LOGOUT_URL": "admin/logout", 160 | "OPERATIONS_SORTER": "method", 161 | "TAGS_SORTER": "alpha", 162 | "DOC_EXPANSION": "none", 163 | } 164 | 165 | SIMPLE_JWT = { 166 | "AUTH_HEADER_TYPES": ("JWT",), 167 | "ACCESS_TOKEN_LIFETIME": timedelta(days=7), 168 | "REFRESH_TOKEN_LIFETIME": timedelta(days=1), 169 | "UPDATE_LAST_LOGIN": True, 170 | } 171 | 172 | REST_FRAMEWORK = { 173 | "DEFAULT_AUTHENTICATION_CLASSES": ( 174 | "rest_framework_simplejwt.authentication.JWTAuthentication", 175 | ), 176 | "DEFAULT_PERMISSION_CLASSES": [ 177 | "rest_framework.permissions.AllowAny", 178 | ], 179 | # "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", #TODO: Pagination Setting Open 1 180 | # "PAGE_SIZE": 5, # TODO: default pagination value:5 181 | } 182 | # -----------------------------END of #3-RD PARTY APPS CONFIG ------------------- 183 | 184 | # ======== CUSTOM CONSTANTS -------------------------------------------------- 185 | 186 | CORS_ALLOW_ALL_ORIGINS = True 187 | # CORS_ALLOW_CREDENTIALS = False 188 | CORS_ALLOW_HEADERS = [ 189 | "accept", 190 | "filename", 191 | "accept-encoding", 192 | "authorization", 193 | "content-type", 194 | "content-disposition", 195 | "dnt", 196 | "boundary", 197 | "origin", 198 | "user-agent", 199 | "x-csrftoken", 200 | "x-requested-with", 201 | "csrfmiddlewaretoken", 202 | ] 203 | 204 | 205 | AUTH_USER_MODEL = "accounts.BaseAccount" 206 | 207 | DOMAIN = "localhost:8081" # TODO: change after frontend deployment 208 | SITE_NAME = "Classroom[LMS]" 209 | -------------------------------------------------------------------------------- /core/settings/dev.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | 3 | # SECURITY WARNING: keep the secret key used in production secret! 4 | SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "Not Exists") 5 | 6 | # TODO: Make this false in production 7 | DEBUG = True 8 | 9 | ALLOWED_HOSTS = ["*", "localhost"] 10 | 11 | DATABASES = { 12 | "default": { 13 | "ENGINE": "django.db.backends.mysql", 14 | "NAME": "classroom", 15 | "USER": "root", 16 | "PASSWORD": "Abcd_1234", 17 | "HOST": "mysql", 18 | "PORT": "3306", 19 | } 20 | # 'default': { 21 | # 'ENGINE': 'django.db.backends.postgresql', 22 | # 'NAME': os.environ.get('POSTGRES_NAME','classroom'), 23 | # 'USER': os.environ.get('POSTGRES_USER','postgres'), 24 | # 'PASSWORD': os.environ.get('POSTGRES_PASSWORD','postgres'), 25 | # 'HOST': 'postgres', 26 | # 'PORT': 5432, 27 | # } 28 | } 29 | 30 | DEV_APPS = [ 31 | "debug_toolbar", 32 | ] 33 | 34 | DEV_MIDDLEWARE = [ 35 | "debug_toolbar.middleware.DebugToolbarMiddleware", 36 | ] 37 | MIDDLEWARE += DEV_MIDDLEWARE 38 | INSTALLED_APPS += DEV_APPS 39 | INTERNAL_IPS = [ 40 | "127.0.0.1", 41 | ] 42 | 43 | # TODO: FAKE-MAIL Comment this in production 44 | # TODO:Mail Settings for Fake Mail 45 | EMAIL_BACKEND = EMAIL_CREDS.EMAIL_BACKEND 46 | # EMAIL_HOST = "localhost" 47 | EMAIL_HOST = "smtp4dev" 48 | EMAIL_PORT = 25 49 | EMAIL_HOST_USER = "classroom@lms.com" 50 | EMAIL_HOST_PASSWORD = "" 51 | # DEFAULT_FROM_EMAIL = "classroom@lms.com" 52 | 53 | # CELERY_BROKER_URL = "redis://localhost:6379/1" 54 | CELERY_BROKER_URL = "redis://redis:6379/1" 55 | 56 | DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": lambda request: True} 57 | -------------------------------------------------------------------------------- /core/settings/prod.py: -------------------------------------------------------------------------------- 1 | from .common import * 2 | import os 3 | import dj_database_url 4 | 5 | 6 | # SECURITY WARNING: keep the secret key used in production secret! 7 | SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") # TODO: SET DJANGO_SECRET_KEY 8 | 9 | DEBUG = False 10 | 11 | ALLOWED_HOSTS = [ 12 | "lms-classroom-api.herokuapp.com" 13 | ] # TODO: allow only localhost , port-> 3000,8000,5000,8081 14 | 15 | DATABASES = {"default": dj_database_url.config()} 16 | 17 | 18 | # TODO: Mail Settings for Original GMAIL 19 | EMAIL_BACKEND = EMAIL_CREDS.EMAIL_BACKEND 20 | EMAIL_HOST = EMAIL_CREDS.EMAIL_HOST 21 | EMAIL_PORT = EMAIL_CREDS.PORT 22 | EMAIL_HOST_USER = EMAIL_CREDS.get_mail_id() 23 | EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD") 24 | EMAIL_USE_TLS = True 25 | TIME_ZONE = "Asia/Kolkata" 26 | 27 | CELERY_BROKER_URL = os.environ["REDIS_URL"] 28 | -------------------------------------------------------------------------------- /core/swagger_schema.py: -------------------------------------------------------------------------------- 1 | from drf_yasg.inspectors import SwaggerAutoSchema 2 | 3 | 4 | class CustomAutoSchema(SwaggerAutoSchema): 5 | def get_tags(self, operation_keys=None): 6 | tags = self.overrides.get("tags", None) or getattr(self.view, "my_tags", []) 7 | if not tags: 8 | tags = [operation_keys[0]] 9 | 10 | return tags 11 | -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | # CORE>URLS 2 | from django.conf import settings 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | from django.urls import path, include 6 | 7 | # DRF_YASG 8 | from rest_framework import permissions 9 | from drf_yasg.views import get_schema_view 10 | from drf_yasg import openapi 11 | 12 | 13 | schema_view = get_schema_view( 14 | openapi.Info( 15 | title="Classroom(LMS) API", 16 | default_version="1.0.0", 17 | description=""" 18 | ## This is the **`Classroom API`** documentation 19 | 20 | > - ### Here all the api routes are grouped by tags 21 | > - ### Classroom, Teachers, DBA, Students, Routers configured 22 | """, 23 | contact=openapi.Contact(email="django.dev.tmsl@gmail.com"), 24 | ), 25 | public=True, 26 | permission_classes=[permissions.AllowAny], 27 | ) 28 | # DRF_YASG 29 | 30 | 31 | urlpatterns = [ 32 | path("classroom-app/", include("classroom.urls")), 33 | path("admin/", admin.site.urls), 34 | path("auth/", include("djoser.urls")), 35 | path("login/", include("djoser.urls.jwt")), 36 | path("", schema_view.with_ui("swagger", cache_timeout=0), name="schema-swagger-ui"), 37 | ] 38 | 39 | if settings.DEBUG: 40 | import debug_toolbar 41 | 42 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 43 | urlpatterns += (path("__debug__/", include(debug_toolbar.urls)),) 44 | 45 | # from termcolor import cprint 46 | # for url in urlpatterns: 47 | # cprint(url,'blue') 48 | # cprint('-------------------------------','blue') 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/4.0/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.dev") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '4.8.2' 2 | services: 3 | web: 4 | build: . 5 | command: ./wait-for-it.sh mysql:3306 -- ./docker-entrypoint.sh 6 | ports: 7 | - 8000:8000 8 | depends_on: 9 | # - mysql 10 | - redis 11 | - celery 12 | - smtp4dev 13 | restart: on-failure 14 | container_name: classroom_api 15 | volumes: 16 | - .:/app 17 | mysql: 18 | image: mysql:8.0 19 | container_name: mysql 20 | ports: 21 | - 3306:3306 22 | restart: always 23 | cap_add: 24 | - SYS_NICE # CAP_SYS_NICE 25 | environment: 26 | - MYSQL_DATABASE=classroom 27 | - MYSQL_ROOT_PASSWORD=Abcd_1234 28 | volumes: 29 | - mysqldata:/var/lib/mysql 30 | redis: 31 | image: redis:6.2-alpine 32 | container_name: redis 33 | ports: 34 | - 6379:6379 35 | restart: always 36 | volumes: 37 | - redisdata:/data 38 | smtp4dev: 39 | image: rnwood/smtp4dev 40 | container_name: email 41 | ports: 42 | - 5000:80 43 | - 25:25 44 | restart: always 45 | celery: 46 | build: . 47 | command: celery -A core worker -l info -P eventlet -E 48 | depends_on: 49 | - redis 50 | container_name: celery 51 | volumes: 52 | - .:/app 53 | volumes: 54 | mysqldata: 55 | redisdata: 56 | # pgdata: 57 | 58 | # celery-beat: 59 | # build: . 60 | # command: celery -A storefront beat --loglevel=info 61 | # depends_on: 62 | # - redis 63 | # volumes: 64 | # - .:/app 65 | # flower: 66 | # build: . 67 | # command: celery -A storefront flower 68 | # depends_on: 69 | # - web 70 | # - redis 71 | # - celery 72 | # environment: 73 | # - DEBUG=1 74 | # - CELERY_BROKER=redis://redis:6379/0 75 | # - CELERY_BACKEND=redis://redis:6379/0 76 | # ports: 77 | # - 5555:5555 78 | # tests: 79 | # build: . 80 | # command: ./wait-for-it.sh mysql:3306 -- ptw 81 | # depends_on: 82 | # - redis 83 | # - mysql 84 | # tty: true 85 | # volumes: 86 | # - .:/app 87 | # postgres: 88 | # image: postgres 89 | # ports: 90 | # - 5432:5432 91 | # volumes: 92 | # - pgdata:/var/lib/postgresql/data 93 | # environment: 94 | # - POSTGRES_DB=classroom 95 | # - POSTGRES_USER=postgres 96 | # - POSTGRES_PASSWORD=postgres -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Apply database migrations 4 | echo "Apply database migrations" 5 | python manage.py migrate 6 | 7 | # Start server 8 | echo "Starting server" 9 | python manage.py runserver 0.0.0.0:8000 10 | 11 | -------------------------------------------------------------------------------- /docs/scripts.md: -------------------------------------------------------------------------------- 1 | ### CMD to Run Server for Frontend 2 | 3 | --- 4 | 5 | ```bash 6 | ngrok http 8000 7 | ``` 8 | 9 | ### FAKE SMTP MAIL 10 | 11 | --- 12 | 13 | ```docker 14 | docker run --rm -it -p 5000:80 -p 2525:25 rnwood/smtp4dev 15 | ``` 16 | 17 | ## Celery - Redis CMDs 18 | 19 | - pipenv run redis 20 | - celery -A core worker -l info -P eventlet 21 | - or pipenv run celery 22 | 23 | ## HEROKU CMDS 24 | 25 | --- 26 | 27 | ```bash 28 | heroku create app-name 29 | ``` 30 | 31 | output: 32 | 33 | ```bash 34 | https://app-url.com/ | https://git.heroku.com/app.git 35 | ``` 36 | 37 | > **`lms-classroom-api.herokuapp.com`** add this in allowed_host of prod.py 38 | 39 | > **`https://git.heroku.com/lms-classroom-api.git`** heroku git repo 40 | 41 | #### config environment variables in heroku 42 | 43 | --- 44 | 45 | ```bash 46 | heroku config:set VARIABLE_NAME='value' 47 | ``` 48 | 49 | > ### deploy app 50 | 51 | --- 52 | 53 | - #### step 1: `git remote -vv` 54 | - #### step 2: `git branch` 55 | - #### step 3: push branch to heroku `git push heroku main` 56 | 57 | # Docker Build & Start 58 | 59 | ```bash 60 | docker-compose up --build 61 | ``` 62 | 63 | ```docker 64 | docker-compose run web python manage.py createsuperuser 65 | ``` 66 | 67 | --- 68 | 69 | ```bash 70 | docker-compose up 71 | ``` 72 | -------------------------------------------------------------------------------- /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.dev") 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 | -------------------------------------------------------------------------------- /readme/BackendAPI_Doc.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/BackendAPI_Doc.jpeg -------------------------------------------------------------------------------- /readme/CreateCollegePage.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/CreateCollegePage.jpeg -------------------------------------------------------------------------------- /readme/DjangoADMIN.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/DjangoADMIN.jpeg -------------------------------------------------------------------------------- /readme/Docker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/Docker.png -------------------------------------------------------------------------------- /readme/HomePage.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/HomePage.jpeg -------------------------------------------------------------------------------- /readme/LogInPage.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/LogInPage.jpeg -------------------------------------------------------------------------------- /readme/Major Project Presentation.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/Major Project Presentation.pptx -------------------------------------------------------------------------------- /readme/Major Project SEM_4 Doc Final Group.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/Major Project SEM_4 Doc Final Group.pdf -------------------------------------------------------------------------------- /readme/SignUpPage.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/SignUpPage.jpeg -------------------------------------------------------------------------------- /readme/SubjectAddByStudent.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/SubjectAddByStudent.jpeg -------------------------------------------------------------------------------- /readme/celery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/celery.png -------------------------------------------------------------------------------- /readme/djangoIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/djangoIcon.png -------------------------------------------------------------------------------- /readme/drf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/drf.png -------------------------------------------------------------------------------- /readme/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/python.png -------------------------------------------------------------------------------- /readme/redis_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/redis_icon.png -------------------------------------------------------------------------------- /readme/smtp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/smtp.png -------------------------------------------------------------------------------- /readme/whole_sw_review.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PritamChk/ClassroomBackend/58a15ca67248e126e92bb9f340b39eff86705a5d/readme/whole_sw_review.mp4 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # These requirements were autogenerated by pipenv 3 | # To regenerate from the project's Pipfile, run: 4 | # 5 | # pipenv lock --requirements 6 | # 7 | 8 | -i https://pypi.org/simple 9 | amqp==5.1.1; python_version >= '3.6' 10 | asgiref==3.5.1; python_version >= '3.7' 11 | async-timeout==4.0.2; python_version >= '3.6' 12 | billiard==3.6.4.0 13 | celery==5.2.6 14 | certifi==2021.10.8 15 | cffi==1.15.0 16 | charset-normalizer==2.0.12; python_version >= '3' 17 | click-didyoumean==0.3.0; python_full_version >= '3.6.2' and python_full_version < '4.0.0' 18 | click-plugins==1.1.1 19 | click-repl==0.2.0 20 | click==8.1.3; python_version >= '3.7' 21 | colorama==0.4.4; platform_system == 'Windows' 22 | coreapi==2.3.3 23 | coreschema==0.0.4 24 | cryptography==37.0.2; python_version >= '3.6' 25 | defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' 26 | deprecated==1.2.13; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 27 | dj-database-url==0.5.0 28 | django-cors-headers==3.12.0 29 | django-extensions==3.1.5 30 | django-templated-mail==1.1.1 31 | django==4.0.4 32 | djangorestframework-simplejwt==4.8.0 33 | djangorestframework==3.13.1 34 | djoser==2.1.0 35 | dnspython==2.2.1; python_version >= '3.6' and python_full_version < '4.0.0' 36 | drf-nested-routers==0.93.4 37 | drf-writable-nested==0.6.3 38 | drf-yasg==1.20.0 39 | et-xmlfile==1.1.0; python_version >= '3.6' 40 | eventlet==0.33.1 41 | greenlet==2.0.0a2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' 42 | gunicorn==20.1.0 43 | idna==3.3; python_version >= '3' 44 | inflection==0.5.1; python_version >= '3.5' 45 | itypes==1.2.0 46 | jinja2==3.1.2; python_version >= '3.7' 47 | kombu==5.2.4; python_version >= '3.7' 48 | markdown==3.3.7 49 | markupsafe==2.1.1; python_version >= '3.7' 50 | numpy==1.22.3; python_version >= '3.10' 51 | oauthlib==3.2.0; python_version >= '3.6' 52 | openpyxl==3.0.9 53 | packaging==21.3; python_version >= '3.6' 54 | pandas==1.4.2 55 | prompt-toolkit==3.0.29; python_full_version >= '3.6.2' 56 | psycopg2==2.9.3 57 | pycparser==2.21 58 | pyjwt==2.4.0; python_version >= '3.6' 59 | pyparsing==3.0.9; python_full_version >= '3.6.8' 60 | python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 61 | python3-openid==3.2.0 62 | pytz==2022.1 63 | redis==4.3.1 64 | requests-oauthlib==1.3.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 65 | requests==2.27.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' 66 | ruamel.yaml.clib==0.2.6; python_version < '3.11' and platform_python_implementation == 'CPython' 67 | ruamel.yaml==0.17.21; python_version >= '3' 68 | setuptools==62.2.0; python_version >= '3.7' 69 | six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' 70 | social-auth-app-django==4.0.0 71 | social-auth-core==4.2.0; python_version >= '3.6' 72 | sqlparse==0.4.2; python_version >= '3.5' 73 | termcolor==1.1.0 74 | tzdata==2022.1; sys_platform == 'win32' 75 | uritemplate==4.1.1; python_version >= '3.6' 76 | urllib3==1.26.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0' 77 | vine==5.0.0; python_version >= '3.6' 78 | wcwidth==0.2.5 79 | whitenoise==6.1.0 80 | wrapt==1.14.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' 81 | -------------------------------------------------------------------------------- /wait-for-it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to test if a given TCP host/port are available 3 | 4 | WAITFORIT_cmdname=${0##*/} 5 | 6 | echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } 7 | 8 | usage() 9 | { 10 | cat << USAGE >&2 11 | Usage: 12 | $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] 13 | -h HOST | --host=HOST Host or IP under test 14 | -p PORT | --port=PORT TCP port under test 15 | Alternatively, you specify the host and port as host:port 16 | -s | --strict Only execute subcommand if the test succeeds 17 | -q | --quiet Don't output any status messages 18 | -t TIMEOUT | --timeout=TIMEOUT 19 | Timeout in seconds, zero for no timeout 20 | -- COMMAND ARGS Execute command with args after the test finishes 21 | USAGE 22 | exit 1 23 | } 24 | 25 | 26 | wait_for() 27 | { 28 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 29 | echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 30 | else 31 | echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" 32 | fi 33 | WAITFORIT_start_ts=$(date +%s) 34 | while : 35 | do 36 | if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then 37 | nc -z $WAITFORIT_HOST $WAITFORIT_PORT 38 | WAITFORIT_result=$? 39 | else 40 | (echo -n > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 41 | WAITFORIT_result=$? 42 | fi 43 | if [[ $WAITFORIT_result -eq 0 ]]; then 44 | WAITFORIT_end_ts=$(date +%s) 45 | echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" 46 | break 47 | fi 48 | sleep 1 49 | done 50 | return $WAITFORIT_result 51 | } 52 | 53 | wait_for_wrapper() 54 | { 55 | # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 56 | if [[ $WAITFORIT_QUIET -eq 1 ]]; then 57 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 58 | else 59 | timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & 60 | fi 61 | WAITFORIT_PID=$! 62 | trap "kill -INT -$WAITFORIT_PID" INT 63 | wait $WAITFORIT_PID 64 | WAITFORIT_RESULT=$? 65 | if [[ $WAITFORIT_RESULT -ne 0 ]]; then 66 | echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" 67 | fi 68 | return $WAITFORIT_RESULT 69 | } 70 | 71 | # process arguments 72 | while [[ $# -gt 0 ]] 73 | do 74 | case "$1" in 75 | *:* ) 76 | WAITFORIT_hostport=(${1//:/ }) 77 | WAITFORIT_HOST=${WAITFORIT_hostport[0]} 78 | WAITFORIT_PORT=${WAITFORIT_hostport[1]} 79 | shift 1 80 | ;; 81 | --child) 82 | WAITFORIT_CHILD=1 83 | shift 1 84 | ;; 85 | -q | --quiet) 86 | WAITFORIT_QUIET=1 87 | shift 1 88 | ;; 89 | -s | --strict) 90 | WAITFORIT_STRICT=1 91 | shift 1 92 | ;; 93 | -h) 94 | WAITFORIT_HOST="$2" 95 | if [[ $WAITFORIT_HOST == "" ]]; then break; fi 96 | shift 2 97 | ;; 98 | --host=*) 99 | WAITFORIT_HOST="${1#*=}" 100 | shift 1 101 | ;; 102 | -p) 103 | WAITFORIT_PORT="$2" 104 | if [[ $WAITFORIT_PORT == "" ]]; then break; fi 105 | shift 2 106 | ;; 107 | --port=*) 108 | WAITFORIT_PORT="${1#*=}" 109 | shift 1 110 | ;; 111 | -t) 112 | WAITFORIT_TIMEOUT="$2" 113 | if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi 114 | shift 2 115 | ;; 116 | --timeout=*) 117 | WAITFORIT_TIMEOUT="${1#*=}" 118 | shift 1 119 | ;; 120 | --) 121 | shift 122 | WAITFORIT_CLI=("$@") 123 | break 124 | ;; 125 | --help) 126 | usage 127 | ;; 128 | *) 129 | echoerr "Unknown argument: $1" 130 | usage 131 | ;; 132 | esac 133 | done 134 | 135 | if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then 136 | echoerr "Error: you need to provide a host and port to test." 137 | usage 138 | fi 139 | 140 | WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} 141 | WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} 142 | WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} 143 | WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} 144 | 145 | # Check to see if timeout is from busybox? 146 | WAITFORIT_TIMEOUT_PATH=$(type -p timeout) 147 | WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) 148 | 149 | WAITFORIT_BUSYTIMEFLAG="" 150 | if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then 151 | WAITFORIT_ISBUSY=1 152 | # Check if busybox timeout uses -t flag 153 | # (recent Alpine versions don't support -t anymore) 154 | if timeout &>/dev/stdout | grep -q -e '-t '; then 155 | WAITFORIT_BUSYTIMEFLAG="-t" 156 | fi 157 | else 158 | WAITFORIT_ISBUSY=0 159 | fi 160 | 161 | if [[ $WAITFORIT_CHILD -gt 0 ]]; then 162 | wait_for 163 | WAITFORIT_RESULT=$? 164 | exit $WAITFORIT_RESULT 165 | else 166 | if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then 167 | wait_for_wrapper 168 | WAITFORIT_RESULT=$? 169 | else 170 | wait_for 171 | WAITFORIT_RESULT=$? 172 | fi 173 | fi 174 | 175 | if [[ $WAITFORIT_CLI != "" ]]; then 176 | if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then 177 | echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" 178 | exit $WAITFORIT_RESULT 179 | fi 180 | exec "${WAITFORIT_CLI[@]}" 181 | else 182 | exit $WAITFORIT_RESULT 183 | fi --------------------------------------------------------------------------------