mirror of
https://github.com/lemeow125/StudE-Backend.git
synced 2024-11-17 06:19:24 +08:00
Merge pull request #1 from lemeow125/feature/websockets
Feature/websockets
This commit is contained in:
commit
c09f502f28
23 changed files with 317 additions and 44 deletions
1
Pipfile
1
Pipfile
|
@ -10,6 +10,7 @@ python-dotenv = "*"
|
||||||
djoser = "*"
|
djoser = "*"
|
||||||
pillow = "*"
|
pillow = "*"
|
||||||
whitenoise = "*"
|
whitenoise = "*"
|
||||||
|
djangochannelsrestframework = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
|
18
Pipfile.lock
generated
18
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "7a186c2a779282b0072d0634e264e5e70842a4c9676dfc4d1e18e564860e87c8"
|
"sha256": "231f11224ec1ef1ad1406ac5644ebcdfac020af9478f407d577553bb05c637df"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
@ -101,6 +101,14 @@
|
||||||
],
|
],
|
||||||
"version": "==1.15.1"
|
"version": "==1.15.1"
|
||||||
},
|
},
|
||||||
|
"channels": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0ce53507a7da7b148eaa454526e0e05f7da5e5d1c23440e4886cf146981d8420",
|
||||||
|
"sha256:2253334ac76f67cba68c2072273f7e0e67dbdac77eeb7e318f511d2f9a53c5e4"
|
||||||
|
],
|
||||||
|
"markers": "python_version >= '3.7'",
|
||||||
|
"version": "==4.0.0"
|
||||||
|
},
|
||||||
"charset-normalizer": {
|
"charset-normalizer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
|
"sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
|
||||||
|
@ -230,6 +238,14 @@
|
||||||
],
|
],
|
||||||
"version": "==1.1.1"
|
"version": "==1.1.1"
|
||||||
},
|
},
|
||||||
|
"djangochannelsrestframework": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:937260996b78fad66ddf4aa03dc61434b81b21a757897a899cd541d0f197c4ce",
|
||||||
|
"sha256:ca37fb96bb2f746129972a81dafed42d9785a37a2db36827dbf17848a0a9df96"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.1.0"
|
||||||
|
},
|
||||||
"djangorestframework": {
|
"djangorestframework": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
|
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
|
||||||
|
|
|
@ -1,10 +1,29 @@
|
||||||
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from .models import CustomUser
|
from .models import CustomUser
|
||||||
|
from year_levels.models import Year_Level
|
||||||
|
from semesters.models import Semester
|
||||||
|
from courses.models import Course
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserForm(forms.ModelForm):
|
||||||
|
year_level = forms.ModelChoiceField(
|
||||||
|
queryset=Year_Level.objects.all(), required=False)
|
||||||
|
semester = forms.ModelChoiceField(
|
||||||
|
queryset=Semester.objects.all(), required=False)
|
||||||
|
course = forms.ModelChoiceField(
|
||||||
|
queryset=Course.objects.all(), required=False)
|
||||||
|
avatar = forms.ImageField(required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomUser
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
class CustomUserAdmin(UserAdmin):
|
class CustomUserAdmin(UserAdmin):
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
|
form = CustomUserForm
|
||||||
|
|
||||||
fieldsets = UserAdmin.fieldsets + (
|
fieldsets = UserAdmin.fieldsets + (
|
||||||
(None, {'fields': ('student_id_number',
|
(None, {'fields': ('student_id_number',
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-04 10:04
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('semesters', '0001_initial'),
|
||||||
|
('courses', '0002_initial'),
|
||||||
|
('year_levels', '0001_initial'),
|
||||||
|
('accounts', '0003_customuser_subjects'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='course',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='courses.course'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='semester',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='semesters.semester'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='year_level',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='year_levels.year_level'),
|
||||||
|
),
|
||||||
|
]
|
18
stude/accounts/migrations/0005_alter_customuser_is_active.py
Normal file
18
stude/accounts/migrations/0005_alter_customuser_is_active.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-04 11:33
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0004_alter_customuser_course_alter_customuser_semester_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='is_active',
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
|
@ -33,6 +33,7 @@ class CustomUser(AbstractUser):
|
||||||
# Username inherited from base user class
|
# Username inherited from base user class
|
||||||
# Password inherited from base user class
|
# Password inherited from base user class
|
||||||
# is_admin inherited from base user class
|
# is_admin inherited from base user class
|
||||||
|
is_active = models.BooleanField(default=False)
|
||||||
is_student = models.BooleanField(default=True)
|
is_student = models.BooleanField(default=True)
|
||||||
is_studying = models.BooleanField(default=False)
|
is_studying = models.BooleanField(default=False)
|
||||||
is_banned = models.BooleanField(default=False)
|
is_banned = models.BooleanField(default=False)
|
||||||
|
@ -41,17 +42,17 @@ class CustomUser(AbstractUser):
|
||||||
avatar = models.ImageField(upload_to=_get_upload_to, null=True)
|
avatar = models.ImageField(upload_to=_get_upload_to, null=True)
|
||||||
course = models.ForeignKey(
|
course = models.ForeignKey(
|
||||||
Course,
|
Course,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
year_level = models.ForeignKey(
|
year_level = models.ForeignKey(
|
||||||
Year_Level,
|
Year_Level,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
semester = models.ForeignKey(
|
semester = models.ForeignKey(
|
||||||
Semester,
|
Semester,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.SET_NULL,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
subjects = models.ManyToManyField(
|
subjects = models.ManyToManyField(
|
||||||
|
@ -72,5 +73,10 @@ def create_superuser(sender, **kwargs):
|
||||||
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
||||||
|
|
||||||
if not User.objects.filter(username=username).exists():
|
if not User.objects.filter(username=username).exists():
|
||||||
User.objects.create_superuser(
|
# Create the superuser with is_active set to False
|
||||||
username, email, password)
|
superuser = User.objects.create_superuser(
|
||||||
|
username=username, email=email, password=password)
|
||||||
|
|
||||||
|
# Activate the superuser
|
||||||
|
superuser.is_active = True
|
||||||
|
superuser.save()
|
||||||
|
|
|
@ -1,34 +1,66 @@
|
||||||
from djoser.serializers import UserCreateSerializer as BaseUserRegistrationSerializer
|
from djoser.serializers import UserCreateSerializer as BaseUserRegistrationSerializer
|
||||||
from djoser.serializers import UserSerializer as BaseUserSerializer
|
from djoser.serializers import UserSerializer as BaseUserSerializer
|
||||||
|
from django.core import exceptions as django_exceptions
|
||||||
|
from rest_framework import exceptions as drf_exceptions
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from accounts.models import CustomUser
|
from accounts.models import CustomUser
|
||||||
from student_status.serializers import StudentStatusSerializer
|
from student_status.serializers import StudentStatusSerializer
|
||||||
from student_status.models import StudentStatus
|
from student_status.models import StudentStatus
|
||||||
|
from rest_framework.settings import api_settings
|
||||||
|
from django.contrib.auth.password_validation import validate_password
|
||||||
|
from courses.models import Course
|
||||||
|
from year_levels.models import Year_Level
|
||||||
|
from semesters.models import Semester
|
||||||
|
|
||||||
|
|
||||||
class CustomUserSerializer(BaseUserSerializer):
|
class CustomUserSerializer(BaseUserSerializer):
|
||||||
user_status = StudentStatusSerializer(
|
user_status = StudentStatusSerializer(
|
||||||
source='studentstatus', read_only=True)
|
source='studentstatus', read_only=True)
|
||||||
|
course = serializers.SlugRelatedField(
|
||||||
|
many=False, slug_field='name', queryset=Course.objects.all(), required=False, allow_null=True)
|
||||||
|
year_level = serializers.SlugRelatedField(
|
||||||
|
many=False, slug_field='name', queryset=Year_Level.objects.all(), required=False, allow_null=True)
|
||||||
|
semester = serializers.SlugRelatedField(
|
||||||
|
many=False, slug_field='name', queryset=Semester.objects.all(), required=False, allow_null=True)
|
||||||
|
|
||||||
class Meta(BaseUserSerializer.Meta):
|
class Meta(BaseUserSerializer.Meta):
|
||||||
model = CustomUser
|
model = CustomUser
|
||||||
fields = ('username', 'email', 'password',
|
fields = ('username', 'email',
|
||||||
'student_id_number', 'year_level', 'semester', 'course', 'subjects', 'avatar', 'first_name', 'last_name', 'is_banned', 'user_status')
|
'student_id_number', 'year_level', 'semester', 'course', 'subjects', 'avatar', 'first_name', 'last_name', 'is_banned', 'user_status')
|
||||||
|
read_only_fields = ('is_banned', 'user_status')
|
||||||
|
|
||||||
|
# The model from your custom user
|
||||||
|
|
||||||
|
|
||||||
class UserRegistrationSerializer(BaseUserRegistrationSerializer):
|
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||||
class Meta(BaseUserRegistrationSerializer.Meta):
|
email = serializers.EmailField(required=True)
|
||||||
fields = ('username', 'email', 'password',
|
student_id_number = serializers.CharField(required=True)
|
||||||
'student_id_number', 'year_level', 'semester', 'course', 'subjects', 'avatar', 'first_name', 'last_name')
|
password = serializers.CharField(write_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomUser # Use your custom user model here
|
||||||
|
fields = ('username', 'email', 'password', 'student_id_number',
|
||||||
|
'year_level', 'semester', 'course', 'subjects', 'avatar',
|
||||||
|
'first_name', 'last_name')
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
user = self.Meta.model(**attrs)
|
||||||
|
password = attrs.get("password")
|
||||||
|
try:
|
||||||
|
validate_password(password, user)
|
||||||
|
except django_exceptions.ValidationError as e:
|
||||||
|
serializer_error = serializers.as_serializer_error(e)
|
||||||
|
raise serializers.ValidationError(
|
||||||
|
{"password": serializer_error[api_settings.NON_FIELD_ERRORS_KEY]}
|
||||||
|
)
|
||||||
|
|
||||||
|
return super().validate(attrs)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Get the user's year_level and semester from the user model instance
|
|
||||||
user = self.Meta.model(**validated_data)
|
user = self.Meta.model(**validated_data)
|
||||||
|
user.set_password(validated_data['password'])
|
||||||
|
user.save()
|
||||||
|
|
||||||
# Create a new user using the base serializer's create() method
|
|
||||||
user = super().create(validated_data)
|
|
||||||
|
|
||||||
# Create a student_status object for the user
|
|
||||||
StudentStatus.objects.create(
|
StudentStatus.objects.create(
|
||||||
user=user,
|
user=user,
|
||||||
active=False,
|
active=False,
|
||||||
|
@ -36,5 +68,4 @@ class UserRegistrationSerializer(BaseUserRegistrationSerializer):
|
||||||
y=None,
|
y=None,
|
||||||
subject=None
|
subject=None
|
||||||
)
|
)
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
|
@ -3,5 +3,5 @@ from django.urls import path, include
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include('djoser.urls')),
|
path('', include('djoser.urls')),
|
||||||
path('', include('djoser.urls.authtoken')),
|
path('', include('djoser.urls.jwt')),
|
||||||
]
|
]
|
||||||
|
|
8
stude/api/routing.py
Normal file
8
stude/api/routing.py
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from django.urls import re_path
|
||||||
|
from channels.routing import URLRouter
|
||||||
|
import student_status.routing
|
||||||
|
import student_status.consumers
|
||||||
|
websocket_urlpatterns = [
|
||||||
|
re_path(r'student_status/',
|
||||||
|
URLRouter(student_status.routing.websocket_urlpatterns))
|
||||||
|
]
|
|
@ -1,16 +1,22 @@
|
||||||
"""
|
|
||||||
ASGI config for stude project.
|
|
||||||
|
|
||||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
|
from channels.auth import AuthMiddlewareStack
|
||||||
from django.core.asgi import get_asgi_application
|
from django.core.asgi import get_asgi_application
|
||||||
|
import api.routing
|
||||||
|
from django.urls import re_path
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||||
|
# Initialize Django ASGI application early to ensure the AppRegistry
|
||||||
|
# is populated before importing code that may import ORM models.
|
||||||
|
django_asgi_app = get_asgi_application()
|
||||||
|
|
||||||
application = get_asgi_application()
|
application = ProtocolTypeRouter({
|
||||||
|
"http": django_asgi_app,
|
||||||
|
# Just HTTP for now. (We can add other protocols later.)
|
||||||
|
'websocket': AuthMiddlewareStack(
|
||||||
|
URLRouter(
|
||||||
|
[re_path(r'ws/', URLRouter(api.routing.websocket_urlpatterns))]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
5
stude/config/email.py
Normal file
5
stude/config/email.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from djoser import email
|
||||||
|
|
||||||
|
|
||||||
|
class ActivationEmail(email.ActivationEmail):
|
||||||
|
template_name = 'email_activation/email_activation.html'
|
|
@ -29,13 +29,14 @@ SECRET_KEY = str(os.getenv('SECRET_KEY'))
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['127.0.0.1', 'localhost']
|
ALLOWED_HOSTS = ['*', '127.0.0.1', 'localhost', '10.0.10.32', '10.0.10.8']
|
||||||
|
|
||||||
# Email credentials
|
# Email credentials
|
||||||
EMAIL_HOST = ''
|
EMAIL_HOST = ''
|
||||||
EMAIL_HOST_USER = ''
|
EMAIL_HOST_USER = ''
|
||||||
EMAIL_HOST_PASSWORD = ''
|
EMAIL_HOST_PASSWORD = ''
|
||||||
EMAIL_PORT = ''
|
EMAIL_PORT = ''
|
||||||
|
EMAIL_USE_TLS = False
|
||||||
|
|
||||||
if (DEBUG == True):
|
if (DEBUG == True):
|
||||||
EMAIL_HOST = str(os.getenv('DEV_EMAIL_HOST'))
|
EMAIL_HOST = str(os.getenv('DEV_EMAIL_HOST'))
|
||||||
|
@ -47,11 +48,13 @@ else:
|
||||||
EMAIL_HOST_USER = str(os.getenv('PROD_EMAIL_HOST_USER'))
|
EMAIL_HOST_USER = str(os.getenv('PROD_EMAIL_HOST_USER'))
|
||||||
EMAIL_HOST_PASSWORD = str(os.getenv('PROD_EMAIL_HOST_PASSWORD'))
|
EMAIL_HOST_PASSWORD = str(os.getenv('PROD_EMAIL_HOST_PASSWORD'))
|
||||||
EMAIL_PORT = str(os.getenv('PROD_EMAIL_PORT'))
|
EMAIL_PORT = str(os.getenv('PROD_EMAIL_PORT'))
|
||||||
|
EMAIL_USE_TLS = str(os.getenv('PROD_EMAIL_TLS'))
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'daphne',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
|
@ -59,8 +62,8 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
|
'rest_framework_simplejwt',
|
||||||
'djoser',
|
'djoser',
|
||||||
'rest_framework.authtoken',
|
|
||||||
'accounts',
|
'accounts',
|
||||||
'student_status',
|
'student_status',
|
||||||
'courses',
|
'courses',
|
||||||
|
@ -88,10 +91,14 @@ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||||
|
|
||||||
ROOT_URLCONF = 'config.urls'
|
ROOT_URLCONF = 'config.urls'
|
||||||
|
|
||||||
|
ASGI_APPLICATION = "config.asgi.application"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
'DIRS': [],
|
'DIRS': [
|
||||||
|
BASE_DIR / 'templates',
|
||||||
|
],
|
||||||
'APP_DIRS': True,
|
'APP_DIRS': True,
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'context_processors': [
|
'context_processors': [
|
||||||
|
@ -106,7 +113,7 @@ TEMPLATES = [
|
||||||
|
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
'rest_framework.authentication.TokenAuthentication',
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +135,14 @@ AUTH_USER_MODEL = 'accounts.CustomUser'
|
||||||
DJOSER = {
|
DJOSER = {
|
||||||
'SEND_ACTIVATION_EMAIL': True,
|
'SEND_ACTIVATION_EMAIL': True,
|
||||||
'SEND_CONFIRMATION_EMAIL': True,
|
'SEND_CONFIRMATION_EMAIL': True,
|
||||||
|
'EMAIL': {
|
||||||
|
'activation': 'config.email.ActivationEmail'
|
||||||
|
},
|
||||||
'ACTIVATION_URL': 'activation/{uid}/{token}',
|
'ACTIVATION_URL': 'activation/{uid}/{token}',
|
||||||
'USER_AUTHENTICATION_RULES': ['djoser.authentication.TokenAuthenticationRule'],
|
'USER_AUTHENTICATION_RULES': ['djoser.authentication.TokenAuthenticationRule'],
|
||||||
'SERIALIZERS': {
|
'SERIALIZERS': {
|
||||||
'user': 'accounts.serializers.CustomUserSerializer',
|
'user': 'accounts.serializers.CustomUserSerializer',
|
||||||
|
'current_user': 'accounts.serializers.CustomUserSerializer',
|
||||||
'user_create': 'accounts.serializers.UserRegistrationSerializer',
|
'user_create': 'accounts.serializers.UserRegistrationSerializer',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -179,3 +190,12 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
DOMAIN = ''
|
||||||
|
if (DEBUG):
|
||||||
|
DOMAIN = 'exp'
|
||||||
|
else:
|
||||||
|
DOMAIN = 'stude'
|
||||||
|
|
||||||
|
|
||||||
|
SITE_NAME = 'Stud-E'
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"""Django's command-line utility for administrative tasks."""
|
"""Django's command-line utility for administrative tasks."""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from django.core.management.commands.runserver import Command as runserver
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -19,4 +20,6 @@ def main():
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
# Override default port for `runserver` command
|
||||||
|
runserver.default_addr = '0.0.0.0'
|
||||||
main()
|
main()
|
||||||
|
|
50
stude/student_status/consumers.py
Normal file
50
stude/student_status/consumers.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# consumers.py
|
||||||
|
import json
|
||||||
|
from .models import StudentStatus
|
||||||
|
from .serializers import StudentStatusSerializer
|
||||||
|
from djangochannelsrestframework.generics import GenericAsyncAPIConsumer
|
||||||
|
from djangochannelsrestframework.decorators import action
|
||||||
|
from djangochannelsrestframework.observer import model_observer, observer
|
||||||
|
from channels.db import database_sync_to_async
|
||||||
|
import asyncio
|
||||||
|
from djangochannelsrestframework.mixins import (
|
||||||
|
ListModelMixin,
|
||||||
|
RetrieveModelMixin,
|
||||||
|
)
|
||||||
|
from djangochannelsrestframework.permissions import IsAuthenticated
|
||||||
|
|
||||||
|
|
||||||
|
class StudentStatusConsumer(
|
||||||
|
ListModelMixin,
|
||||||
|
RetrieveModelMixin,
|
||||||
|
GenericAsyncAPIConsumer,
|
||||||
|
):
|
||||||
|
permission_classes = [IsAuthenticated]
|
||||||
|
queryset = StudentStatus.objects.filter(active=True)
|
||||||
|
serializer_class = StudentStatusSerializer
|
||||||
|
|
||||||
|
async def websocket_connect(self, message):
|
||||||
|
# This method is called when the websocket is handshaking as part of the connection process.
|
||||||
|
await self.accept()
|
||||||
|
self.send_updates_task = asyncio.create_task(self.send_updates())
|
||||||
|
|
||||||
|
async def websocket_disconnect(self, message):
|
||||||
|
# This method is called when the WebSocket closes for any reason.
|
||||||
|
# Here we want to cancel our periodic task that sends updates
|
||||||
|
self.send_updates_task.cancel()
|
||||||
|
|
||||||
|
@database_sync_to_async
|
||||||
|
def get_student_statuses(self):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
return StudentStatusSerializer(queryset, many=True).data
|
||||||
|
|
||||||
|
async def send_updates(self):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data = await self.get_student_statuses()
|
||||||
|
# print(f"Sending update: {data}") Debug
|
||||||
|
await self.send(text_data=json.dumps(data))
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Exception in send_updates: {e}")
|
||||||
|
break # Break the loop on error
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 4.2.2 on 2023-07-04 10:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('subjects', '0002_subjectstudent_subject_students'),
|
||||||
|
('student_status', '0004_alter_studentstatus_study_group'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='studentstatus',
|
||||||
|
name='subject',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='subjects.subject'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -11,7 +11,7 @@ class StudentStatus(models.Model):
|
||||||
x = models.FloatField(null=True)
|
x = models.FloatField(null=True)
|
||||||
y = models.FloatField(null=True)
|
y = models.FloatField(null=True)
|
||||||
subject = models.ForeignKey(
|
subject = models.ForeignKey(
|
||||||
'subjects.Subject', on_delete=models.CASCADE, null=True)
|
'subjects.Subject', on_delete=models.SET_NULL, null=True)
|
||||||
active = models.BooleanField(default=False)
|
active = models.BooleanField(default=False)
|
||||||
timestamp = models.DateField(auto_now_add=True)
|
timestamp = models.DateField(auto_now_add=True)
|
||||||
study_group = models.ManyToManyField(
|
study_group = models.ManyToManyField(
|
||||||
|
|
7
stude/student_status/routing.py
Normal file
7
stude/student_status/routing.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# routing.py
|
||||||
|
from django.urls import re_path
|
||||||
|
from . import consumers
|
||||||
|
|
||||||
|
websocket_urlpatterns = [
|
||||||
|
re_path(r"active", consumers.StudentStatusConsumer.as_asgi()),
|
||||||
|
]
|
|
@ -3,9 +3,11 @@ from .models import StudentStatus
|
||||||
|
|
||||||
|
|
||||||
class StudentStatusSerializer(serializers.ModelSerializer):
|
class StudentStatusSerializer(serializers.ModelSerializer):
|
||||||
year_level = serializers.CharField(source='user.year_level', read_only=True)
|
year_level = serializers.CharField(
|
||||||
|
source='user.year_level', read_only=True)
|
||||||
course = serializers.CharField(source='user.course', read_only=True)
|
course = serializers.CharField(source='user.course', read_only=True)
|
||||||
semester = serializers.CharField(source='user.semester', read_only=True)
|
semester = serializers.CharField(source='user.semester', read_only=True)
|
||||||
|
user = serializers.CharField(source='user.full_name', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StudentStatus
|
model = StudentStatus
|
||||||
|
|
|
@ -3,5 +3,5 @@ from .views import StudentStatusAPIView, ActiveStudentStatusListAPIView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('self/', StudentStatusAPIView.as_view()),
|
path('self/', StudentStatusAPIView.as_view()),
|
||||||
path('active_list/', ActiveStudentStatusListAPIView.as_view()),
|
path('list/', ActiveStudentStatusListAPIView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import StudyGroup, StudyGroupMembership
|
from .models import StudyGroup, StudyGroupMembership
|
||||||
from accounts.models import CustomUser
|
from accounts.models import CustomUser
|
||||||
|
from subjects.models import Subject
|
||||||
|
|
||||||
|
|
||||||
class StudyGroupSerializer(serializers.ModelSerializer):
|
class StudyGroupSerializer(serializers.ModelSerializer):
|
||||||
users = serializers.SlugRelatedField(
|
users = serializers.SlugRelatedField(
|
||||||
queryset=CustomUser.objects.all(), many=True, slug_field='name', allow_null=True)
|
queryset=CustomUser.objects.all(), many=True, slug_field='name', required=False, allow_null=True)
|
||||||
subject = serializers.CharField(source='subject.Subject', read_only=True)
|
subject = serializers.SlugRelatedField(
|
||||||
|
many=False, slug_field='name', queryset=Subject.objects.all(), required=True, allow_null=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = StudyGroup
|
model = StudyGroup
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from .models import Message
|
from .models import Message
|
||||||
from accounts.models import CustomUser
|
from accounts.models import CustomUser
|
||||||
|
from study_groups.models import StudyGroup
|
||||||
|
|
||||||
|
|
||||||
class MessageSerializer(serializers.ModelSerializer):
|
class MessageSerializer(serializers.ModelSerializer):
|
||||||
user = serializers.SlugRelatedField(
|
user = serializers.SlugRelatedField(
|
||||||
queryset=CustomUser.objects.all(), slug_field='full_name', required=False)
|
queryset=CustomUser.objects.all(), slug_field='full_name', required=True)
|
||||||
study_group = serializers.CharField(
|
study_group = serializers.SlugRelatedField(
|
||||||
source='subject.Subject', read_only=True, required=False)
|
queryset=StudyGroup.objects.all(), slug_field='name', required=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Message
|
model = Message
|
||||||
|
|
26
stude/templates/email_activation/email_activation.html
Normal file
26
stude/templates/email_activation/email_activation.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block subject %}
|
||||||
|
{% blocktrans %}Account activation on {{ site_name }}{% endblocktrans %}
|
||||||
|
{% endblock subject %}
|
||||||
|
|
||||||
|
{% block text_body %}
|
||||||
|
{% blocktrans %}You're receiving this email because you need to finish activation process on {{ site_name }}.{% endblocktrans %}
|
||||||
|
|
||||||
|
{% trans "Please open the following link to activate your account in-app:" %}
|
||||||
|
{{ domain }}://{{ url|safe }}
|
||||||
|
|
||||||
|
{% trans "Thanks you for using StudE!" %}
|
||||||
|
|
||||||
|
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
|
||||||
|
{% endblock text_body %}
|
||||||
|
|
||||||
|
{% block html_body %}
|
||||||
|
<p>{% blocktrans %}You're receiving this email because you need to finish activation process on {{ site_name }}.{% endblocktrans %}</p>
|
||||||
|
|
||||||
|
<p>{% trans "Please go to the following page to activate your account in-app:" %}</p>
|
||||||
|
<p><a href="{{ domain }}://{{ url|safe }}">{{ domain }}://--/{{ url|safe }}</a></p>
|
||||||
|
|
||||||
|
<p>{% blocktrans %}Many thanks from the {{ site_name }} team{% endblocktrans %}</p>
|
||||||
|
|
||||||
|
{% endblock html_body %}
|
|
@ -24,6 +24,6 @@ def populate_courses(sender, **kwargs):
|
||||||
name='3rd Year', shortname='3rdYr')
|
name='3rd Year', shortname='3rdYr')
|
||||||
Year_Level.objects.get_or_create(
|
Year_Level.objects.get_or_create(
|
||||||
name='4th Year', shortname='4thYr')
|
name='4th Year', shortname='4thYr')
|
||||||
Year_Level.objects.get_or_create(
|
# Year_Level.objects.get_or_create(
|
||||||
name='Irregular', shortname='Irreg')
|
# name='Irregular', shortname='Irreg')
|
||||||
# Add more predefined records as needed
|
# Add more predefined records as needed
|
||||||
|
|
Loading…
Reference in a new issue