Bulk import students, bulk change course
This commit is contained in:
@@ -1,15 +1,21 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.contrib import admin
|
from django.contrib import admin, messages
|
||||||
from django.contrib import messages
|
|
||||||
from django.utils.translation import ngettext
|
from django.utils.translation import ngettext
|
||||||
from django.utils.safestring import SafeText
|
from django.utils.safestring import SafeText
|
||||||
from durationwidget.widgets import TimeDurationWidget
|
|
||||||
from datetime import date
|
|
||||||
|
|
||||||
import nested_admin
|
import nested_admin
|
||||||
|
|
||||||
|
from durationwidget.widgets import TimeDurationWidget
|
||||||
|
|
||||||
|
from import_export import fields
|
||||||
|
from import_export.admin import ImportMixin
|
||||||
|
from import_export.resources import ModelResource
|
||||||
|
from import_export.widgets import CharWidget
|
||||||
|
|
||||||
|
from django_admin_action_forms import AdminActionFormsMixin, AdminActionForm, action_with_form
|
||||||
|
|
||||||
from .models.courses import Course
|
from .models.courses import Course
|
||||||
from .models.hourbuildings import HourBuilding, HourBuildingLeg
|
from .models.hourbuildings import HourBuilding, HourBuildingLeg
|
||||||
from .models.missions import Training, MissionProfile
|
from .models.missions import Training, MissionProfile
|
||||||
@@ -17,10 +23,10 @@ from .models.students import Student
|
|||||||
from .models.weekpref import WeekPreference
|
from .models.weekpref import WeekPreference
|
||||||
|
|
||||||
from .custom.colortag import course_color
|
from .custom.colortag import course_color
|
||||||
from .custom.defpassword import default_password
|
|
||||||
|
|
||||||
from .actions.exportweek import export_selected
|
from .actions.exportweek import export_selected
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
class TrainingForm(forms.ModelForm):
|
class TrainingForm(forms.ModelForm):
|
||||||
model=Training
|
model=Training
|
||||||
|
|
||||||
@@ -198,10 +204,38 @@ class WeekPreferenceAdmin(nested_admin.NestedModelAdmin):
|
|||||||
obj.student = request.user.student
|
obj.student = request.user.student
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
class StudentAdmin(admin.ModelAdmin):
|
|
||||||
|
# Resource Class for Student data import
|
||||||
|
class StudentResource(ModelResource):
|
||||||
|
surname = fields.Field(attribute="surname", column_name="surname", widget=CharWidget())
|
||||||
|
name = fields.Field(attribute="name", column_name="name", widget=CharWidget())
|
||||||
|
email = fields.Field(attribute="email", column_name="email", widget=CharWidget())
|
||||||
|
phone = fields.Field(attribute="phone", column_name="phone", widget=CharWidget())
|
||||||
|
|
||||||
|
# Cleanup fields before entering
|
||||||
|
def before_import_row(self, row: dict[str, str], **kwargs) -> None:
|
||||||
|
row['name'] = SafeText(row['name'].capitalize().strip())
|
||||||
|
row['surname'] = SafeText(row['surname'].capitalize().strip())
|
||||||
|
row['phone'] = SafeText(row['phone'].replace(' ',''))
|
||||||
|
row['email'] = SafeText(row['email'].lower().strip())
|
||||||
|
return super().before_import_row(row, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Student
|
||||||
|
skip_unchanged = True
|
||||||
|
report_skipped = True
|
||||||
|
fields = ('surname', 'name', 'email', 'phone')
|
||||||
|
import_id_fields = ['email', 'phone']
|
||||||
|
|
||||||
|
# Form Class for Student course change
|
||||||
|
class ChangeCourseForm(AdminActionForm):
|
||||||
|
course = forms.ModelChoiceField(queryset=Course.objects.all())
|
||||||
|
|
||||||
|
class StudentAdmin(ImportMixin, AdminActionFormsMixin, admin.ModelAdmin):
|
||||||
list_display = ("surname", "name", "course", "course_color", "email", "phone", "password", "active")
|
list_display = ("surname", "name", "course", "course_color", "email", "phone", "password", "active")
|
||||||
list_filter = ("course", "active")
|
list_filter = ("course", "active")
|
||||||
actions = ("disable_students",)
|
actions = ("change_course", "disable_students")
|
||||||
|
resource_classes = [StudentResource]
|
||||||
|
|
||||||
@admin.display(description="Color")
|
@admin.display(description="Color")
|
||||||
def course_color(self, obj: Student) -> SafeText:
|
def course_color(self, obj: Student) -> SafeText:
|
||||||
@@ -211,12 +245,23 @@ class StudentAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.display(description="Password")
|
@admin.display(description="Password")
|
||||||
def password(self, obj: Student) -> SafeText:
|
def password(self, obj: Student) -> SafeText:
|
||||||
return SafeText(default_password(student=obj))
|
return SafeText(obj.default_password())
|
||||||
|
|
||||||
@admin.action(description="Disable Students")
|
@admin.action(description="Disable Students")
|
||||||
def disable_students(modeladmin, request: HttpRequest, queryset: QuerySet[Student]):
|
def disable_students(self, request: HttpRequest, queryset: QuerySet[Student]):
|
||||||
queryset.update(active = False)
|
for q in queryset.all():
|
||||||
|
if q.user:
|
||||||
|
q.user.is_staff = False
|
||||||
|
q.user.save()
|
||||||
|
count: int = queryset.update(active = False)
|
||||||
|
messages.success(request, f"{count} students deactivated")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@action_with_form(ChangeCourseForm, description="Change Student Course")
|
||||||
|
def change_course(self, request: HttpRequest, queryset: QuerySet[Student], data):
|
||||||
|
course = data["course"]
|
||||||
|
count: int = queryset.update(course=course)
|
||||||
|
messages.success(request, f"{count} students updated to {course}")
|
||||||
|
|
||||||
class CourseAdmin(admin.ModelAdmin):
|
class CourseAdmin(admin.ModelAdmin):
|
||||||
list_display = ("ctype", "cnumber","color_display", "year")
|
list_display = ("ctype", "cnumber","color_display", "year")
|
||||||
|
|||||||
@@ -3,7 +3,3 @@ from django.apps import AppConfig
|
|||||||
class FlightslotConfig(AppConfig):
|
class FlightslotConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'flightslot'
|
name = 'flightslot'
|
||||||
|
|
||||||
def ready(self):
|
|
||||||
# Import only when application is ready otherwise signals will not be called
|
|
||||||
from . import signals
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
from ..models.students import Student
|
|
||||||
|
|
||||||
def default_password(student: Student) -> str:
|
|
||||||
return f"{student.name.lower()[0]}{student.surname.lower()}{student.id}"
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User, Group
|
||||||
|
|
||||||
from ..models.courses import Course
|
from ..models.courses import Course
|
||||||
|
|
||||||
@@ -46,5 +46,35 @@ class Student(models.Model):
|
|||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def default_password(self) -> str:
|
||||||
|
return f"{self.name.lower()[0]}{self.surname.lower()}{self.id}"
|
||||||
|
|
||||||
|
# Override save method to add user for login upon Student creation
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
creating = self.pk is None
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
if creating and not self.user:
|
||||||
|
username = f"{self.name.lower()}.{self.surname.lower()}"
|
||||||
|
# Avoid username conflict with progressive number
|
||||||
|
base_username = username
|
||||||
|
counter = 1
|
||||||
|
while User.objects.filter(username=username).exists():
|
||||||
|
username = f"{base_username}{counter}"
|
||||||
|
counter += 1
|
||||||
|
# Create user
|
||||||
|
user = User.objects.create_user(
|
||||||
|
first_name=self.name,
|
||||||
|
last_name=self.surname,
|
||||||
|
username=username,
|
||||||
|
email=self.email,
|
||||||
|
password=self.default_password(),
|
||||||
|
is_staff=True
|
||||||
|
)
|
||||||
|
|
||||||
|
student_group, _ = Group.objects.get_or_create(name="StudentGroup")
|
||||||
|
user.groups.add(student_group)
|
||||||
|
self.user = user
|
||||||
|
self.save()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.surname} {self.name[0]}. => {self.course}"
|
return f"{self.surname} {self.name[0]}. => {self.course}"
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
from django.db.models.signals import post_save
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.auth.models import Group
|
|
||||||
from .models.students import Student
|
|
||||||
from .custom.defpassword import default_password
|
|
||||||
|
|
||||||
|
|
||||||
# Create a Django user every time a new student is created
|
|
||||||
@receiver(post_save, sender=Student)
|
|
||||||
def create_user_for_student(sender: Student, student: Student, created, **kwargs):
|
|
||||||
if created and not student.user:
|
|
||||||
username = f"{student.name.lower()}.{student.surname.lower()}"
|
|
||||||
# Avoid username conflict with progressive number
|
|
||||||
base_username = username
|
|
||||||
counter = 1
|
|
||||||
while User.objects.filter(username=username).exists():
|
|
||||||
username = f"{base_username}{counter}"
|
|
||||||
counter += 1
|
|
||||||
# Create user
|
|
||||||
user = User.objects.create_user(
|
|
||||||
username=username,
|
|
||||||
email=student.email,
|
|
||||||
password=default_password(student=student)
|
|
||||||
)
|
|
||||||
|
|
||||||
student_group, _ = Group.objects.get_or_create(name="StudentGroup")
|
|
||||||
user.groups.add(student_group)
|
|
||||||
student.user = user
|
|
||||||
student.save()
|
|
||||||
17
techdb/poetry.lock
generated
17
techdb/poetry.lock
generated
@@ -51,6 +51,21 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
|||||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||||
bcrypt = ["bcrypt"]
|
bcrypt = ["bcrypt"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-admin-action-forms"
|
||||||
|
version = "2.2.1"
|
||||||
|
description = "Extension for the Django admin panel that allows passing additional parameters to actions by creating intermediate pages with forms."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "django_admin_action_forms-2.2.1-py3-none-any.whl", hash = "sha256:597d20d36fcb6cfbb0b5e0ed83df0c9dbd5af6b225f7af24f5b96a2ed84d4d35"},
|
||||||
|
{file = "django_admin_action_forms-2.2.1.tar.gz", hash = "sha256:94ff59964ece5d6b8d2c9c307f22837863be173e3fb64fdcd64d6f301e1d0c9d"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
django = ">=3.2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-colorfield"
|
name = "django-colorfield"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -406,4 +421,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "e231b5570d8b02b46736a58612eab986373b3231b23437cad90d489ea97ecb5b"
|
content-hash = "6bf43236f441d8b6bf8d1928910d169d3b29cfa499bb7d09d97ea227f8115658"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ django-durationwidget = "^1.0.5"
|
|||||||
django-import-export = "^4.3.13"
|
django-import-export = "^4.3.13"
|
||||||
django-colorfield = "^0.14.0"
|
django-colorfield = "^0.14.0"
|
||||||
openpyxl = "^3.1.5"
|
openpyxl = "^3.1.5"
|
||||||
|
django-admin-action-forms = "^2.2.1"
|
||||||
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
@@ -41,9 +41,17 @@ INSTALLED_APPS = [
|
|||||||
'nested_admin',
|
'nested_admin',
|
||||||
'flightslot',
|
'flightslot',
|
||||||
'durationwidget',
|
'durationwidget',
|
||||||
'colorfield'
|
'colorfield',
|
||||||
|
'import_export',
|
||||||
|
'django_admin_action_forms',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Import Export plugin settings
|
||||||
|
IMPORT_EXPORT_USE_TRANSACTIONS = True
|
||||||
|
IMPORT_EXPORT_SKIP_ADMIN_LOG = True
|
||||||
|
from import_export.formats.base_formats import CSV, XLSX
|
||||||
|
IMPORT_FORMATS = [CSV, XLSX]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.middleware.security.SecurityMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
|||||||
Reference in New Issue
Block a user