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 = "*"
|
||||
pillow = "*"
|
||||
whitenoise = "*"
|
||||
djangochannelsrestframework = "*"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
|
|
18
Pipfile.lock
generated
18
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "7a186c2a779282b0072d0634e264e5e70842a4c9676dfc4d1e18e564860e87c8"
|
||||
"sha256": "231f11224ec1ef1ad1406ac5644ebcdfac020af9478f407d577553bb05c637df"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -101,6 +101,14 @@
|
|||
],
|
||||
"version": "==1.15.1"
|
||||
},
|
||||
"channels": {
|
||||
"hashes": [
|
||||
"sha256:0ce53507a7da7b148eaa454526e0e05f7da5e5d1c23440e4886cf146981d8420",
|
||||
"sha256:2253334ac76f67cba68c2072273f7e0e67dbdac77eeb7e318f511d2f9a53c5e4"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"charset-normalizer": {
|
||||
"hashes": [
|
||||
"sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
|
||||
|
@ -230,6 +238,14 @@
|
|||
],
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"djangochannelsrestframework": {
|
||||
"hashes": [
|
||||
"sha256:937260996b78fad66ddf4aa03dc61434b81b21a757897a899cd541d0f197c4ce",
|
||||
"sha256:ca37fb96bb2f746129972a81dafed42d9785a37a2db36827dbf17848a0a9df96"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"djangorestframework": {
|
||||
"hashes": [
|
||||
"sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8",
|
||||
|
|
|
@ -1,10 +1,29 @@
|
|||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
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):
|
||||
model = CustomUser
|
||||
form = CustomUserForm
|
||||
|
||||
fieldsets = UserAdmin.fieldsets + (
|
||||
(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
|
||||
# Password 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_studying = 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)
|
||||
course = models.ForeignKey(
|
||||
Course,
|
||||
on_delete=models.CASCADE,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True
|
||||
)
|
||||
year_level = models.ForeignKey(
|
||||
Year_Level,
|
||||
on_delete=models.CASCADE,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True
|
||||
)
|
||||
semester = models.ForeignKey(
|
||||
Semester,
|
||||
on_delete=models.CASCADE,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True
|
||||
)
|
||||
subjects = models.ManyToManyField(
|
||||
|
@ -72,5 +73,10 @@ def create_superuser(sender, **kwargs):
|
|||
password = os.getenv('DJANGO_ADMIN_PASSWORD')
|
||||
|
||||
if not User.objects.filter(username=username).exists():
|
||||
User.objects.create_superuser(
|
||||
username, email, password)
|
||||
# Create the superuser with is_active set to False
|
||||
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 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 accounts.models import CustomUser
|
||||
from student_status.serializers import StudentStatusSerializer
|
||||
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):
|
||||
user_status = StudentStatusSerializer(
|
||||
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):
|
||||
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')
|
||||
read_only_fields = ('is_banned', 'user_status')
|
||||
|
||||
# The model from your custom user
|
||||
|
||||
|
||||
class UserRegistrationSerializer(BaseUserRegistrationSerializer):
|
||||
class Meta(BaseUserRegistrationSerializer.Meta):
|
||||
fields = ('username', 'email', 'password',
|
||||
'student_id_number', 'year_level', 'semester', 'course', 'subjects', 'avatar', 'first_name', 'last_name')
|
||||
class UserRegistrationSerializer(serializers.ModelSerializer):
|
||||
email = serializers.EmailField(required=True)
|
||||
student_id_number = serializers.CharField(required=True)
|
||||
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):
|
||||
# Get the user's year_level and semester from the user model instance
|
||||
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(
|
||||
user=user,
|
||||
active=False,
|
||||
|
@ -36,5 +68,4 @@ class UserRegistrationSerializer(BaseUserRegistrationSerializer):
|
|||
y=None,
|
||||
subject=None
|
||||
)
|
||||
|
||||
return user
|
||||
|
|
|
@ -3,5 +3,5 @@ from django.urls import path, include
|
|||
|
||||
urlpatterns = [
|
||||
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
|
||||
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
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')
|
||||
# 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!
|
||||
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_HOST = ''
|
||||
EMAIL_HOST_USER = ''
|
||||
EMAIL_HOST_PASSWORD = ''
|
||||
EMAIL_PORT = ''
|
||||
EMAIL_USE_TLS = False
|
||||
|
||||
if (DEBUG == True):
|
||||
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_PASSWORD = str(os.getenv('PROD_EMAIL_HOST_PASSWORD'))
|
||||
EMAIL_PORT = str(os.getenv('PROD_EMAIL_PORT'))
|
||||
EMAIL_USE_TLS = str(os.getenv('PROD_EMAIL_TLS'))
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'daphne',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
|
@ -59,8 +62,8 @@ INSTALLED_APPS = [
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
'rest_framework_simplejwt',
|
||||
'djoser',
|
||||
'rest_framework.authtoken',
|
||||
'accounts',
|
||||
'student_status',
|
||||
'courses',
|
||||
|
@ -88,10 +91,14 @@ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
|||
|
||||
ROOT_URLCONF = 'config.urls'
|
||||
|
||||
ASGI_APPLICATION = "config.asgi.application"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'DIRS': [
|
||||
BASE_DIR / 'templates',
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
@ -106,7 +113,7 @@ TEMPLATES = [
|
|||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.TokenAuthentication',
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -128,10 +135,14 @@ AUTH_USER_MODEL = 'accounts.CustomUser'
|
|||
DJOSER = {
|
||||
'SEND_ACTIVATION_EMAIL': True,
|
||||
'SEND_CONFIRMATION_EMAIL': True,
|
||||
'EMAIL': {
|
||||
'activation': 'config.email.ActivationEmail'
|
||||
},
|
||||
'ACTIVATION_URL': 'activation/{uid}/{token}',
|
||||
'USER_AUTHENTICATION_RULES': ['djoser.authentication.TokenAuthenticationRule'],
|
||||
'SERIALIZERS': {
|
||||
'user': 'accounts.serializers.CustomUserSerializer',
|
||||
'current_user': 'accounts.serializers.CustomUserSerializer',
|
||||
'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
|
||||
|
||||
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."""
|
||||
import os
|
||||
import sys
|
||||
from django.core.management.commands.runserver import Command as runserver
|
||||
|
||||
|
||||
def main():
|
||||
|
@ -19,4 +20,6 @@ def main():
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Override default port for `runserver` command
|
||||
runserver.default_addr = '0.0.0.0'
|
||||
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)
|
||||
y = models.FloatField(null=True)
|
||||
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)
|
||||
timestamp = models.DateField(auto_now_add=True)
|
||||
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):
|
||||
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)
|
||||
semester = serializers.CharField(source='user.semester', read_only=True)
|
||||
user = serializers.CharField(source='user.full_name', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = StudentStatus
|
||||
|
@ -17,7 +19,7 @@ class StudentStatusSerializer(serializers.ModelSerializer):
|
|||
student_status = StudentStatus.objects.create(
|
||||
user=user, defaults=validated_data)
|
||||
return student_status
|
||||
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
active = validated_data.get('active', None)
|
||||
|
||||
|
@ -26,4 +28,4 @@ class StudentStatusSerializer(serializers.ModelSerializer):
|
|||
validated_data['y'] = None
|
||||
validated_data['subject'] = None
|
||||
|
||||
return super().update(instance, validated_data)
|
||||
return super().update(instance, validated_data)
|
||||
|
|
|
@ -3,5 +3,5 @@ from .views import StudentStatusAPIView, ActiveStudentStatusListAPIView
|
|||
|
||||
urlpatterns = [
|
||||
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 .models import StudyGroup, StudyGroupMembership
|
||||
from accounts.models import CustomUser
|
||||
from subjects.models import Subject
|
||||
|
||||
|
||||
class StudyGroupSerializer(serializers.ModelSerializer):
|
||||
users = serializers.SlugRelatedField(
|
||||
queryset=CustomUser.objects.all(), many=True, slug_field='name', allow_null=True)
|
||||
subject = serializers.CharField(source='subject.Subject', read_only=True)
|
||||
queryset=CustomUser.objects.all(), many=True, slug_field='name', required=False, allow_null=True)
|
||||
subject = serializers.SlugRelatedField(
|
||||
many=False, slug_field='name', queryset=Subject.objects.all(), required=True, allow_null=False)
|
||||
|
||||
class Meta:
|
||||
model = StudyGroup
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
from rest_framework import serializers
|
||||
from .models import Message
|
||||
from accounts.models import CustomUser
|
||||
from study_groups.models import StudyGroup
|
||||
|
||||
|
||||
class MessageSerializer(serializers.ModelSerializer):
|
||||
user = serializers.SlugRelatedField(
|
||||
queryset=CustomUser.objects.all(), slug_field='full_name', required=False)
|
||||
study_group = serializers.CharField(
|
||||
source='subject.Subject', read_only=True, required=False)
|
||||
queryset=CustomUser.objects.all(), slug_field='full_name', required=True)
|
||||
study_group = serializers.SlugRelatedField(
|
||||
queryset=StudyGroup.objects.all(), slug_field='name', required=True)
|
||||
|
||||
class Meta:
|
||||
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')
|
||||
Year_Level.objects.get_or_create(
|
||||
name='4th Year', shortname='4thYr')
|
||||
Year_Level.objects.get_or_create(
|
||||
name='Irregular', shortname='Irreg')
|
||||
# Year_Level.objects.get_or_create(
|
||||
# name='Irregular', shortname='Irreg')
|
||||
# Add more predefined records as needed
|
||||
|
|
Loading…
Reference in a new issue