Spaces:
Sleeping
Sleeping
Upload 36 files
Browse files- .dockerignore +15 -0
- .gitignore +15 -0
- Dockerfile +20 -0
- config/__init__.py +0 -0
- config/__pycache__/__init__.cpython-313.pyc +0 -0
- config/__pycache__/settings.cpython-313.pyc +0 -0
- config/__pycache__/urls.cpython-313.pyc +0 -0
- config/__pycache__/wsgi.cpython-313.pyc +0 -0
- config/settings.py +93 -0
- config/urls.py +11 -0
- config/wsgi.py +6 -0
- docker-compose.yml +15 -0
- manage.py +20 -0
- requirements.txt +5 -0
- students/__init__.py +0 -0
- students/__pycache__/__init__.cpython-313.pyc +0 -0
- students/__pycache__/admin.cpython-313.pyc +0 -0
- students/__pycache__/apps.cpython-313.pyc +0 -0
- students/__pycache__/models.cpython-313.pyc +0 -0
- students/__pycache__/serializers.cpython-313.pyc +0 -0
- students/__pycache__/urls.cpython-313.pyc +0 -0
- students/__pycache__/views.cpython-313.pyc +0 -0
- students/admin.py +10 -0
- students/apps.py +6 -0
- students/migrations/0001_initial.py +30 -0
- students/migrations/__init__.py +0 -0
- students/migrations/__pycache__/0001_initial.cpython-313.pyc +0 -0
- students/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
- students/models.py +17 -0
- students/serializers.py +9 -0
- students/templates/students/base.html +28 -0
- students/templates/students/student_confirm_delete.html +13 -0
- students/templates/students/student_form.html +29 -0
- students/templates/students/student_list.html +42 -0
- students/urls.py +16 -0
- students/views.py +98 -0
.dockerignore
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
| 5 |
+
.Python
|
| 6 |
+
env
|
| 7 |
+
venv
|
| 8 |
+
.env
|
| 9 |
+
.git
|
| 10 |
+
.gitignore
|
| 11 |
+
README.md
|
| 12 |
+
*.md
|
| 13 |
+
.vscode
|
| 14 |
+
.idea
|
| 15 |
+
*.log
|
.gitignore
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
| 5 |
+
.Python
|
| 6 |
+
env
|
| 7 |
+
venv
|
| 8 |
+
.env
|
| 9 |
+
*.log
|
| 10 |
+
db.sqlite3
|
| 11 |
+
media/
|
| 12 |
+
staticfiles/
|
| 13 |
+
.vscode
|
| 14 |
+
.idea
|
| 15 |
+
*.md
|
Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 4 |
+
ENV PYTHONUNBUFFERED=1
|
| 5 |
+
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
RUN apt-get update && apt-get install -y \
|
| 9 |
+
default-libmysqlclient-dev \
|
| 10 |
+
build-essential \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
COPY requirements.txt /app/
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
+
|
| 16 |
+
COPY . /app/
|
| 17 |
+
|
| 18 |
+
EXPOSE 7860
|
| 19 |
+
|
| 20 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "config.wsgi:application"]
|
config/__init__.py
ADDED
|
File without changes
|
config/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (133 Bytes). View file
|
|
|
config/__pycache__/settings.cpython-313.pyc
ADDED
|
Binary file (3.08 kB). View file
|
|
|
config/__pycache__/urls.cpython-313.pyc
ADDED
|
Binary file (793 Bytes). View file
|
|
|
config/__pycache__/wsgi.cpython-313.pyc
ADDED
|
Binary file (383 Bytes). View file
|
|
|
config/settings.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
| 8 |
+
|
| 9 |
+
SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-dev-key-change-in-production')
|
| 10 |
+
|
| 11 |
+
DEBUG = os.getenv('DEBUG', 'True') == 'True'
|
| 12 |
+
|
| 13 |
+
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '*').split(',')
|
| 14 |
+
|
| 15 |
+
INSTALLED_APPS = [
|
| 16 |
+
'django.contrib.admin',
|
| 17 |
+
'django.contrib.auth',
|
| 18 |
+
'django.contrib.contenttypes',
|
| 19 |
+
'django.contrib.sessions',
|
| 20 |
+
'django.contrib.messages',
|
| 21 |
+
'django.contrib.staticfiles',
|
| 22 |
+
'rest_framework',
|
| 23 |
+
'students',
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
MIDDLEWARE = [
|
| 27 |
+
'django.middleware.security.SecurityMiddleware',
|
| 28 |
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
| 29 |
+
'django.middleware.common.CommonMiddleware',
|
| 30 |
+
'django.middleware.csrf.CsrfViewMiddleware',
|
| 31 |
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
| 32 |
+
'django.contrib.messages.middleware.MessageMiddleware',
|
| 33 |
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
ROOT_URLCONF = 'config.urls'
|
| 37 |
+
|
| 38 |
+
TEMPLATES = [
|
| 39 |
+
{
|
| 40 |
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
| 41 |
+
'DIRS': [],
|
| 42 |
+
'APP_DIRS': True,
|
| 43 |
+
'OPTIONS': {
|
| 44 |
+
'context_processors': [
|
| 45 |
+
'django.template.context_processors.debug',
|
| 46 |
+
'django.template.context_processors.request',
|
| 47 |
+
'django.contrib.auth.context_processors.auth',
|
| 48 |
+
'django.contrib.messages.context_processors.messages',
|
| 49 |
+
],
|
| 50 |
+
},
|
| 51 |
+
},
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
+
WSGI_APPLICATION = 'config.wsgi.application'
|
| 55 |
+
|
| 56 |
+
DATABASES = {
|
| 57 |
+
'default': {
|
| 58 |
+
'ENGINE': 'django.db.backends.mysql',
|
| 59 |
+
'NAME': os.getenv('DB_NAME', 'appdb'),
|
| 60 |
+
'USER': os.getenv('DB_USER', 'root'),
|
| 61 |
+
'PASSWORD': os.getenv('DB_PASSWORD', 'root'),
|
| 62 |
+
'HOST': os.getenv('DB_HOST', '118.69.117.220'),
|
| 63 |
+
'PORT': os.getenv('DB_PORT', '1611'),
|
| 64 |
+
'OPTIONS': {
|
| 65 |
+
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
|
| 66 |
+
'charset': 'utf8mb4',
|
| 67 |
+
},
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
AUTH_PASSWORD_VALIDATORS = [
|
| 72 |
+
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
|
| 73 |
+
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator'},
|
| 74 |
+
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
|
| 75 |
+
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
|
| 76 |
+
]
|
| 77 |
+
|
| 78 |
+
LANGUAGE_CODE = 'en-us'
|
| 79 |
+
TIME_ZONE = 'UTC'
|
| 80 |
+
USE_I18N = True
|
| 81 |
+
USE_TZ = True
|
| 82 |
+
|
| 83 |
+
STATIC_URL = 'static/'
|
| 84 |
+
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
| 85 |
+
|
| 86 |
+
REST_FRAMEWORK = {
|
| 87 |
+
'DEFAULT_RENDERER_CLASSES': [
|
| 88 |
+
'rest_framework.renderers.JSONRenderer',
|
| 89 |
+
],
|
| 90 |
+
'DEFAULT_PARSER_CLASSES': [
|
| 91 |
+
'rest_framework.parsers.JSONParser',
|
| 92 |
+
],
|
| 93 |
+
}
|
config/urls.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from django.urls import path, include
|
| 3 |
+
from django.shortcuts import redirect
|
| 4 |
+
from students.urls import ui_urlpatterns
|
| 5 |
+
|
| 6 |
+
urlpatterns = [
|
| 7 |
+
path('admin/', admin.site.urls),
|
| 8 |
+
path('api/students/', include('students.urls')),
|
| 9 |
+
path('students/', include((ui_urlpatterns, 'students'), namespace='students')),
|
| 10 |
+
path('', lambda request: redirect('students:student-list-ui')),
|
| 11 |
+
]
|
config/wsgi.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from django.core.wsgi import get_wsgi_application
|
| 3 |
+
|
| 4 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
| 5 |
+
|
| 6 |
+
application = get_wsgi_application()
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: '3.8'
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
web:
|
| 5 |
+
build: .
|
| 6 |
+
ports:
|
| 7 |
+
- "7860:7860"
|
| 8 |
+
env_file:
|
| 9 |
+
- .env
|
| 10 |
+
volumes:
|
| 11 |
+
- .:/app
|
| 12 |
+
command: >
|
| 13 |
+
sh -c "python manage.py migrate &&
|
| 14 |
+
python manage.py runserver 0.0.0.0:7860"
|
| 15 |
+
restart: unless-stopped
|
manage.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def main():
|
| 7 |
+
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
| 8 |
+
try:
|
| 9 |
+
from django.core.management import execute_from_command_line
|
| 10 |
+
except ImportError as exc:
|
| 11 |
+
raise ImportError(
|
| 12 |
+
"Couldn't import Django. Are you sure it's installed and "
|
| 13 |
+
"available on your PYTHONPATH environment variable? Did you "
|
| 14 |
+
"forget to activate a virtual environment?"
|
| 15 |
+
) from exc
|
| 16 |
+
execute_from_command_line(sys.argv)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
if __name__ == '__main__':
|
| 20 |
+
main()
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Django==5.0.6
|
| 2 |
+
djangorestframework==3.15.1
|
| 3 |
+
mysqlclient==2.2.4
|
| 4 |
+
python-dotenv==1.0.1
|
| 5 |
+
gunicorn==22.0.0
|
students/__init__.py
ADDED
|
File without changes
|
students/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (135 Bytes). View file
|
|
|
students/__pycache__/admin.cpython-313.pyc
ADDED
|
Binary file (775 Bytes). View file
|
|
|
students/__pycache__/apps.cpython-313.pyc
ADDED
|
Binary file (503 Bytes). View file
|
|
|
students/__pycache__/models.cpython-313.pyc
ADDED
|
Binary file (1.29 kB). View file
|
|
|
students/__pycache__/serializers.cpython-313.pyc
ADDED
|
Binary file (858 Bytes). View file
|
|
|
students/__pycache__/urls.cpython-313.pyc
ADDED
|
Binary file (1.04 kB). View file
|
|
|
students/__pycache__/views.cpython-313.pyc
ADDED
|
Binary file (6.39 kB). View file
|
|
|
students/admin.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.contrib import admin
|
| 2 |
+
from .models import Student
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
@admin.register(Student)
|
| 6 |
+
class StudentAdmin(admin.ModelAdmin):
|
| 7 |
+
list_display = ['id', 'name', 'email', 'age', 'course', 'created_at']
|
| 8 |
+
list_filter = ['course', 'created_at']
|
| 9 |
+
search_fields = ['name', 'email']
|
| 10 |
+
readonly_fields = ['created_at', 'updated_at']
|
students/apps.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.apps import AppConfig
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class StudentsConfig(AppConfig):
|
| 5 |
+
default_auto_field = 'django.db.models.BigAutoField'
|
| 6 |
+
name = 'students'
|
students/migrations/0001_initial.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 6.0.6 on 2026-06-21 02:21
|
| 2 |
+
|
| 3 |
+
from django.db import migrations, models
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
initial = True
|
| 9 |
+
|
| 10 |
+
dependencies = [
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
operations = [
|
| 14 |
+
migrations.CreateModel(
|
| 15 |
+
name='Student',
|
| 16 |
+
fields=[
|
| 17 |
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
| 18 |
+
('name', models.CharField(max_length=100)),
|
| 19 |
+
('email', models.EmailField(max_length=254, unique=True)),
|
| 20 |
+
('age', models.IntegerField()),
|
| 21 |
+
('course', models.CharField(max_length=100)),
|
| 22 |
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
| 23 |
+
('updated_at', models.DateTimeField(auto_now=True)),
|
| 24 |
+
],
|
| 25 |
+
options={
|
| 26 |
+
'db_table': 'students',
|
| 27 |
+
'ordering': ['-created_at'],
|
| 28 |
+
},
|
| 29 |
+
),
|
| 30 |
+
]
|
students/migrations/__init__.py
ADDED
|
File without changes
|
students/migrations/__pycache__/0001_initial.cpython-313.pyc
ADDED
|
Binary file (1.36 kB). View file
|
|
|
students/migrations/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (146 Bytes). View file
|
|
|
students/models.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.db import models
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class Student(models.Model):
|
| 5 |
+
name = models.CharField(max_length=100)
|
| 6 |
+
email = models.EmailField(unique=True)
|
| 7 |
+
age = models.IntegerField()
|
| 8 |
+
course = models.CharField(max_length=100)
|
| 9 |
+
created_at = models.DateTimeField(auto_now_add=True)
|
| 10 |
+
updated_at = models.DateTimeField(auto_now=True)
|
| 11 |
+
|
| 12 |
+
class Meta:
|
| 13 |
+
db_table = 'students'
|
| 14 |
+
ordering = ['-created_at']
|
| 15 |
+
|
| 16 |
+
def __str__(self):
|
| 17 |
+
return self.name
|
students/serializers.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from rest_framework import serializers
|
| 2 |
+
from .models import Student
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class StudentSerializer(serializers.ModelSerializer):
|
| 6 |
+
class Meta:
|
| 7 |
+
model = Student
|
| 8 |
+
fields = ['id', 'name', 'email', 'age', 'course', 'created_at', 'updated_at']
|
| 9 |
+
read_only_fields = ['id', 'created_at', 'updated_at']
|
students/templates/students/base.html
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{% block title %}Student Management{% endblock %}</title>
|
| 7 |
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<nav class="navbar navbar-expand-lg navbar-dark bg-primary mb-4">
|
| 11 |
+
<div class="container">
|
| 12 |
+
<a class="navbar-brand" href="{% url 'students:student-list-ui' %}">Student Management</a>
|
| 13 |
+
</div>
|
| 14 |
+
</nav>
|
| 15 |
+
<div class="container">
|
| 16 |
+
{% if messages %}
|
| 17 |
+
{% for message in messages %}
|
| 18 |
+
<div class="alert alert-{{ message.tags }} alert-dismissible fade show">
|
| 19 |
+
{{ message }}
|
| 20 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
| 21 |
+
</div>
|
| 22 |
+
{% endfor %}
|
| 23 |
+
{% endif %}
|
| 24 |
+
{% block content %}{% endblock %}
|
| 25 |
+
</div>
|
| 26 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
| 27 |
+
</body>
|
| 28 |
+
</html>
|
students/templates/students/student_confirm_delete.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'students/base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Delete Student{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<h1>Delete Student</h1>
|
| 7 |
+
<p class="mt-3">Are you sure you want to delete <strong>{{ student.name }}</strong>?</p>
|
| 8 |
+
<form method="post">
|
| 9 |
+
{% csrf_token %}
|
| 10 |
+
<button type="submit" class="btn btn-danger">Yes, delete</button>
|
| 11 |
+
<a href="{% url 'students:student-list-ui' %}" class="btn btn-secondary">Cancel</a>
|
| 12 |
+
</form>
|
| 13 |
+
{% endblock %}
|
students/templates/students/student_form.html
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'students/base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}{{ title }}{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<h1>{{ title }}</h1>
|
| 7 |
+
|
| 8 |
+
<form method="post" class="mt-3">
|
| 9 |
+
{% csrf_token %}
|
| 10 |
+
<div class="mb-3">
|
| 11 |
+
<label class="form-label">Name</label>
|
| 12 |
+
<input type="text" name="name" class="form-control" value="{{ student.name|default:'' }}" required>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="mb-3">
|
| 15 |
+
<label class="form-label">Email</label>
|
| 16 |
+
<input type="email" name="email" class="form-control" value="{{ student.email|default:'' }}" required>
|
| 17 |
+
</div>
|
| 18 |
+
<div class="mb-3">
|
| 19 |
+
<label class="form-label">Age</label>
|
| 20 |
+
<input type="number" name="age" class="form-control" value="{{ student.age|default:'' }}" required>
|
| 21 |
+
</div>
|
| 22 |
+
<div class="mb-3">
|
| 23 |
+
<label class="form-label">Course</label>
|
| 24 |
+
<input type="text" name="course" class="form-control" value="{{ student.course|default:'' }}" required>
|
| 25 |
+
</div>
|
| 26 |
+
<button type="submit" class="btn btn-success">Save</button>
|
| 27 |
+
<a href="{% url 'students:student-list-ui' %}" class="btn btn-secondary">Cancel</a>
|
| 28 |
+
</form>
|
| 29 |
+
{% endblock %}
|
students/templates/students/student_list.html
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'students/base.html' %}
|
| 2 |
+
|
| 3 |
+
{% block title %}Student List{% endblock %}
|
| 4 |
+
|
| 5 |
+
{% block content %}
|
| 6 |
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
| 7 |
+
<h1>Students</h1>
|
| 8 |
+
<a href="{% url 'students:student-create-ui' %}" class="btn btn-primary">+ Add Student</a>
|
| 9 |
+
</div>
|
| 10 |
+
|
| 11 |
+
<table class="table table-striped table-hover">
|
| 12 |
+
<thead class="table-dark">
|
| 13 |
+
<tr>
|
| 14 |
+
<th>ID</th>
|
| 15 |
+
<th>Name</th>
|
| 16 |
+
<th>Email</th>
|
| 17 |
+
<th>Age</th>
|
| 18 |
+
<th>Course</th>
|
| 19 |
+
<th>Actions</th>
|
| 20 |
+
</tr>
|
| 21 |
+
</thead>
|
| 22 |
+
<tbody>
|
| 23 |
+
{% for student in students %}
|
| 24 |
+
<tr>
|
| 25 |
+
<td>{{ student.id }}</td>
|
| 26 |
+
<td>{{ student.name }}</td>
|
| 27 |
+
<td>{{ student.email }}</td>
|
| 28 |
+
<td>{{ student.age }}</td>
|
| 29 |
+
<td>{{ student.course }}</td>
|
| 30 |
+
<td>
|
| 31 |
+
<a href="{% url 'students:student-update-ui' student.id %}" class="btn btn-sm btn-warning">Edit</a>
|
| 32 |
+
<a href="{% url 'students:student-delete-ui' student.id %}" class="btn btn-sm btn-danger">Delete</a>
|
| 33 |
+
</td>
|
| 34 |
+
</tr>
|
| 35 |
+
{% empty %}
|
| 36 |
+
<tr>
|
| 37 |
+
<td colspan="6" class="text-center">No students found.</td>
|
| 38 |
+
</tr>
|
| 39 |
+
{% endfor %}
|
| 40 |
+
</tbody>
|
| 41 |
+
</table>
|
| 42 |
+
{% endblock %}
|
students/urls.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.urls import path
|
| 2 |
+
from . import views
|
| 3 |
+
|
| 4 |
+
urlpatterns = [
|
| 5 |
+
path('', views.student_list, name='student-list'),
|
| 6 |
+
path('create/', views.student_create, name='student-create'),
|
| 7 |
+
path('<int:pk>/', views.student_detail, name='student-detail'),
|
| 8 |
+
path('<int:pk>/delete/', views.student_delete, name='student-delete'),
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
ui_urlpatterns = [
|
| 12 |
+
path('', views.student_list_ui, name='student-list-ui'),
|
| 13 |
+
path('add/', views.student_create_ui, name='student-create-ui'),
|
| 14 |
+
path('<int:pk>/edit/', views.student_update_ui, name='student-update-ui'),
|
| 15 |
+
path('<int:pk>/delete/', views.student_delete_ui, name='student-delete-ui'),
|
| 16 |
+
]
|
students/views.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from django.shortcuts import render, redirect, get_object_or_404
|
| 2 |
+
from django.contrib import messages
|
| 3 |
+
from rest_framework import status
|
| 4 |
+
from rest_framework.decorators import api_view
|
| 5 |
+
from rest_framework.response import Response
|
| 6 |
+
from .models import Student
|
| 7 |
+
from .serializers import StudentSerializer
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@api_view(['GET'])
|
| 11 |
+
def student_list(request):
|
| 12 |
+
students = Student.objects.all()
|
| 13 |
+
serializer = StudentSerializer(students, many=True)
|
| 14 |
+
return Response(serializer.data)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
@api_view(['POST'])
|
| 18 |
+
def student_create(request):
|
| 19 |
+
serializer = StudentSerializer(data=request.data)
|
| 20 |
+
if serializer.is_valid():
|
| 21 |
+
serializer.save()
|
| 22 |
+
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
| 23 |
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@api_view(['GET', 'PUT', 'PATCH'])
|
| 27 |
+
def student_detail(request, pk):
|
| 28 |
+
try:
|
| 29 |
+
student = Student.objects.get(pk=pk)
|
| 30 |
+
except Student.DoesNotExist:
|
| 31 |
+
return Response({'error': 'Student not found'}, status=status.HTTP_404_NOT_FOUND)
|
| 32 |
+
|
| 33 |
+
if request.method == 'GET':
|
| 34 |
+
serializer = StudentSerializer(student)
|
| 35 |
+
return Response(serializer.data)
|
| 36 |
+
|
| 37 |
+
elif request.method in ['PUT', 'PATCH']:
|
| 38 |
+
serializer = StudentSerializer(student, data=request.data, partial=(request.method == 'PATCH'))
|
| 39 |
+
if serializer.is_valid():
|
| 40 |
+
serializer.save()
|
| 41 |
+
return Response(serializer.data)
|
| 42 |
+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@api_view(['DELETE'])
|
| 46 |
+
def student_delete(request, pk):
|
| 47 |
+
try:
|
| 48 |
+
student = Student.objects.get(pk=pk)
|
| 49 |
+
except Student.DoesNotExist:
|
| 50 |
+
return Response({'error': 'Student not found'}, status=status.HTTP_404_NOT_FOUND)
|
| 51 |
+
|
| 52 |
+
student.delete()
|
| 53 |
+
return Response(status=status.HTTP_204_NO_CONTENT)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def student_list_ui(request):
|
| 57 |
+
students = Student.objects.all()
|
| 58 |
+
return render(request, 'students/student_list.html', {'students': students})
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def student_create_ui(request):
|
| 62 |
+
if request.method == 'POST':
|
| 63 |
+
name = request.POST.get('name')
|
| 64 |
+
email = request.POST.get('email')
|
| 65 |
+
age = request.POST.get('age')
|
| 66 |
+
course = request.POST.get('course')
|
| 67 |
+
try:
|
| 68 |
+
Student.objects.create(name=name, email=email, age=int(age), course=course)
|
| 69 |
+
messages.success(request, 'Student added successfully.')
|
| 70 |
+
return redirect('students:student-list-ui')
|
| 71 |
+
except Exception as e:
|
| 72 |
+
messages.error(request, f'Error: {e}')
|
| 73 |
+
return render(request, 'students/student_form.html', {'title': 'Add Student'})
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def student_update_ui(request, pk):
|
| 77 |
+
student = get_object_or_404(Student, pk=pk)
|
| 78 |
+
if request.method == 'POST':
|
| 79 |
+
student.name = request.POST.get('name')
|
| 80 |
+
student.email = request.POST.get('email')
|
| 81 |
+
student.age = int(request.POST.get('age'))
|
| 82 |
+
student.course = request.POST.get('course')
|
| 83 |
+
try:
|
| 84 |
+
student.save()
|
| 85 |
+
messages.success(request, 'Student updated successfully.')
|
| 86 |
+
return redirect('students:student-list-ui')
|
| 87 |
+
except Exception as e:
|
| 88 |
+
messages.error(request, f'Error: {e}')
|
| 89 |
+
return render(request, 'students/student_form.html', {'title': 'Edit Student', 'student': student})
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def student_delete_ui(request, pk):
|
| 93 |
+
student = get_object_or_404(Student, pk=pk)
|
| 94 |
+
if request.method == 'POST':
|
| 95 |
+
student.delete()
|
| 96 |
+
messages.success(request, 'Student deleted successfully.')
|
| 97 |
+
return redirect('students:student-list-ui')
|
| 98 |
+
return render(request, 'students/student_confirm_delete.html', {'student': student})
|