add django extensions, add faker, add Django model factories for accounts

This commit is contained in:
WajahatKanju 2024-02-06 22:31:13 +05:00
parent bce9774594
commit 2d1be28a4c
5 changed files with 391 additions and 222 deletions

View File

@ -1,215 +1,215 @@
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.contrib.auth.models import AbstractUser, UserManager from django.contrib.auth.models import AbstractUser, UserManager
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from PIL import Image from PIL import Image
from course.models import Program from course.models import Program
from .validators import ASCIIUsernameValidator from .validators import ASCIIUsernameValidator
# LEVEL_COURSE = "Level course" # LEVEL_COURSE = "Level course"
BACHLOAR_DEGREE = "Bachloar" BACHLOAR_DEGREE = "Bachloar"
MASTER_DEGREE = "Master" MASTER_DEGREE = "Master"
LEVEL = ( LEVEL = (
# (LEVEL_COURSE, "Level course"), # (LEVEL_COURSE, "Level course"),
(BACHLOAR_DEGREE, "Bachloar Degree"), (BACHLOAR_DEGREE, "Bachloar Degree"),
(MASTER_DEGREE, "Master Degree"), (MASTER_DEGREE, "Master Degree"),
) )
FATHER = "Father" FATHER = "Father"
MOTHER = "Mother" MOTHER = "Mother"
BROTHER = "Brother" BROTHER = "Brother"
SISTER = "Sister" SISTER = "Sister"
GRAND_MOTHER = "Grand mother" GRAND_MOTHER = "Grand mother"
GRAND_FATHER = "Grand father" GRAND_FATHER = "Grand father"
OTHER = "Other" OTHER = "Other"
RELATION_SHIP = ( RELATION_SHIP = (
(FATHER, "Father"), (FATHER, "Father"),
(MOTHER, "Mother"), (MOTHER, "Mother"),
(BROTHER, "Brother"), (BROTHER, "Brother"),
(SISTER, "Sister"), (SISTER, "Sister"),
(GRAND_MOTHER, "Grand mother"), (GRAND_MOTHER, "Grand mother"),
(GRAND_FATHER, "Grand father"), (GRAND_FATHER, "Grand father"),
(OTHER, "Other"), (OTHER, "Other"),
) )
class CustomUserManager(UserManager): class CustomUserManager(UserManager):
def search(self, query=None): def search(self, query=None):
queryset = self.get_queryset() queryset = self.get_queryset()
if query is not None: if query is not None:
or_lookup = ( or_lookup = (
Q(username__icontains=query) Q(username__icontains=query)
| Q(first_name__icontains=query) | Q(first_name__icontains=query)
| Q(last_name__icontains=query) | Q(last_name__icontains=query)
| Q(email__icontains=query) | Q(email__icontains=query)
) )
queryset = queryset.filter( queryset = queryset.filter(
or_lookup or_lookup
).distinct() # distinct() is often necessary with Q lookups ).distinct() # distinct() is often necessary with Q lookups
return queryset return queryset
GENDERS = (("M", "Male"), ("F", "Female")) GENDERS = (("M", "Male"), ("F", "Female"))
class User(AbstractUser): class User(AbstractUser):
is_student = models.BooleanField(default=False) is_student = models.BooleanField(default=False)
is_lecturer = models.BooleanField(default=False) is_lecturer = models.BooleanField(default=False)
is_parent = models.BooleanField(default=False) is_parent = models.BooleanField(default=False)
is_dep_head = models.BooleanField(default=False) is_dep_head = models.BooleanField(default=False)
gender = models.CharField(max_length=1, choices=GENDERS, blank=True, null=True) gender = models.CharField(max_length=1, choices=GENDERS, blank=True, null=True)
phone = models.CharField(max_length=60, blank=True, null=True) phone = models.CharField(max_length=60, blank=True, null=True)
address = models.CharField(max_length=60, blank=True, null=True) address = models.CharField(max_length=60, blank=True, null=True)
picture = models.ImageField( picture = models.ImageField(
upload_to="profile_pictures/%y/%m/%d/", default="default.png", null=True upload_to="profile_pictures/%y/%m/%d/", default="default.png", null=True
) )
email = models.EmailField(blank=True, null=True) email = models.EmailField(blank=True, null=True)
username_validator = ASCIIUsernameValidator() username_validator = ASCIIUsernameValidator()
objects = CustomUserManager() objects = CustomUserManager()
class Meta: class Meta:
ordering = ("-date_joined",) ordering = ("-date_joined",)
@property @property
def get_full_name(self): def get_full_name(self):
full_name = self.username full_name = self.username
if self.first_name and self.last_name: if self.first_name and self.last_name:
full_name = self.first_name + " " + self.last_name full_name = self.first_name + " " + self.last_name
return full_name return full_name
@classmethod @classmethod
def get_student_count(cls): def get_student_count(cls):
return cls.objects.filter(is_student=True).count() return cls.objects.filter(is_student=True).count()
@classmethod @classmethod
def get_lecturer_count(cls): def get_lecturer_count(cls):
return cls.objects.filter(is_lecturer=True).count() return cls.objects.filter(is_lecturer=True).count()
@classmethod @classmethod
def get_superuser_count(cls): def get_superuser_count(cls):
return cls.objects.filter(is_superuser=True).count() return cls.objects.filter(is_superuser=True).count()
def __str__(self): def __str__(self):
return "{} ({})".format(self.username, self.get_full_name) return "{} ({})".format(self.username, self.get_full_name)
@property @property
def get_user_role(self): def get_user_role(self):
if self.is_superuser: if self.is_superuser:
role = "Admin" role = "Admin"
elif self.is_student: elif self.is_student:
role = "Student" role = "Student"
elif self.is_lecturer: elif self.is_lecturer:
role = "Lecturer" role = "Lecturer"
elif self.is_parent: elif self.is_parent:
role = "Parent" role = "Parent"
return role return role
def get_picture(self): def get_picture(self):
try: try:
return self.picture.url return self.picture.url
except: except:
no_picture = settings.MEDIA_URL + "default.png" no_picture = settings.MEDIA_URL + "default.png"
return no_picture return no_picture
def get_absolute_url(self): def get_absolute_url(self):
return reverse("profile_single", kwargs={"id": self.id}) return reverse("profile_single", kwargs={"id": self.id})
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
super().save(*args, **kwargs) super().save(*args, **kwargs)
try: try:
img = Image.open(self.picture.path) img = Image.open(self.picture.path)
if img.height > 300 or img.width > 300: if img.height > 300 or img.width > 300:
output_size = (300, 300) output_size = (300, 300)
img.thumbnail(output_size) img.thumbnail(output_size)
img.save(self.picture.path) img.save(self.picture.path)
except: except:
pass pass
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
if self.picture.url != settings.MEDIA_URL + "default.png": if self.picture.url != settings.MEDIA_URL + "default.png":
self.picture.delete() self.picture.delete()
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
class StudentManager(models.Manager): class StudentManager(models.Manager):
def search(self, query=None): def search(self, query=None):
qs = self.get_queryset() qs = self.get_queryset()
if query is not None: if query is not None:
or_lookup = Q(level__icontains=query) | Q(program__icontains=query) or_lookup = Q(level__icontains=query) | Q(program__icontains=query)
qs = qs.filter( qs = qs.filter(
or_lookup or_lookup
).distinct() # distinct() is often necessary with Q lookups ).distinct() # distinct() is often necessary with Q lookups
return qs return qs
class Student(models.Model): class Student(models.Model):
student = models.OneToOneField(User, on_delete=models.CASCADE) student = models.OneToOneField(User, on_delete=models.CASCADE)
# id_number = models.CharField(max_length=20, unique=True, blank=True) # id_number = models.CharField(max_length=20, unique=True, blank=True)
level = models.CharField(max_length=25, choices=LEVEL, null=True) level = models.CharField(max_length=25, choices=LEVEL, null=True)
program = models.ForeignKey(Program, on_delete=models.CASCADE, null=True) program = models.ForeignKey(Program, on_delete=models.CASCADE, null=True)
objects = StudentManager() objects = StudentManager()
class Meta: class Meta:
ordering = ("-student__date_joined",) ordering = ("-student__date_joined",)
def __str__(self): def __str__(self):
return self.student.get_full_name return self.student.get_full_name
@classmethod @classmethod
def get_gender_count(cls): def get_gender_count(cls):
males_count = Student.objects.filter(student__gender="M").count() males_count = Student.objects.filter(student__gender="M").count()
females_count = Student.objects.filter(student__gender="F").count() females_count = Student.objects.filter(student__gender="F").count()
return {"M": males_count, "F": females_count} return {"M": males_count, "F": females_count}
def get_absolute_url(self): def get_absolute_url(self):
return reverse("profile_single", kwargs={"id": self.id}) return reverse("profile_single", kwargs={"id": self.id})
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
self.student.delete() self.student.delete()
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
class Parent(models.Model): class Parent(models.Model):
""" """
Connect student with their parent, parents can Connect student with their parent, parents can
only view their connected students information only view their connected students information
""" """
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
student = models.OneToOneField(Student, null=True, on_delete=models.SET_NULL) student = models.OneToOneField(Student, null=True, on_delete=models.SET_NULL)
first_name = models.CharField(max_length=120) first_name = models.CharField(max_length=120)
last_name = models.CharField(max_length=120) last_name = models.CharField(max_length=120)
phone = models.CharField(max_length=60, blank=True, null=True) phone = models.CharField(max_length=60, blank=True, null=True)
email = models.EmailField(blank=True, null=True) email = models.EmailField(blank=True, null=True)
# What is the relationship between the student and # What is the relationship between the student and
# the parent (i.e. father, mother, brother, sister) # the parent (i.e. father, mother, brother, sister)
relation_ship = models.TextField(choices=RELATION_SHIP, blank=True) relation_ship = models.TextField(choices=RELATION_SHIP, blank=True)
class Meta: class Meta:
ordering = ("-user__date_joined",) ordering = ("-user__date_joined",)
def __str__(self): def __str__(self):
return self.user.username return self.user.username
class DepartmentHead(models.Model): class DepartmentHead(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
department = models.ForeignKey(Program, on_delete=models.CASCADE, null=True) department = models.ForeignKey(Program, on_delete=models.CASCADE, null=True)
class Meta: class Meta:
ordering = ("-user__date_joined",) ordering = ("-user__date_joined",)
def __str__(self): def __str__(self):
return "{}".format(self.user) return "{}".format(self.user)

View File

@ -37,6 +37,7 @@ AUTH_USER_MODEL = "accounts.User"
DJANGO_APPS = [ DJANGO_APPS = [
"jet.dashboard", "jet.dashboard",
"jet", "jet",
"django_extensions",
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",

View File

@ -1,7 +1,10 @@
-r base.txt -r base.txt
psycopg2-binary==2.9.5 # https://github.com/psycopg/psycopg2 psycopg2-binary==2.9.5 # https://github.com/psycopg/psycopg2
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
black==22.12.0 # https://github.com/psf/black black==22.12.0 # https://github.com/psf/black
# Django Extensions
django-extensions==3.1.3 # https://github.com/django-extensions/django-extensions

0
scripts/__init__.py Normal file
View File

View File

@ -0,0 +1,165 @@
import os
import django
from typing import List, Tuple
from django.utils import timezone
from faker import Faker
from factory.django import DjangoModelFactory
from factory import SubFactory, LazyAttribute, Iterator
from django_extensions.management.commands import runscript
# Set up Django environment
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
django.setup()
from accounts.models import User, Student, Parent, DepartmentHead, LEVEL, RELATION_SHIP
from course.models import Program
fake = Faker()
class UserFactory(DjangoModelFactory):
"""
Factory for creating User instances with optional flags.
Attributes:
username (str): The generated username.
first_name (str): The generated first name.
last_name (str): The generated last name.
email (str): The generated email.
date_joined (datetime): The current date and time.
phone (str): The generated phone number.
address (str): The generated address.
is_student (bool): Flag indicating if the user is a student.
is_lecturer (bool): Flag indicating if the user is a lecturer.
is_parent (bool): Flag indicating if the user is a parent.
is_dep_head (bool): Flag indicating if the user is a department head.
"""
class Meta:
model = User
username: str = LazyAttribute(lambda x: fake.user_name())
first_name: str = LazyAttribute(lambda x: fake.first_name())
last_name: str = LazyAttribute(lambda x: fake.last_name())
email: str = LazyAttribute(lambda x: fake.email())
date_joined: timezone.datetime = timezone.now()
phone: str = LazyAttribute(lambda x: fake.phone_number())
address: str = LazyAttribute(lambda x: fake.address())
is_student: bool = False
is_lecturer: bool = False
is_parent: bool = False
is_dep_head: bool = False
@classmethod
def _create(cls, model_class: type, *args, **kwargs) -> User:
"""
Create a User instance with optional flags.
Args:
model_class (type): The class of the model to create.
Returns:
User: The created User instance.
"""
user: User = super()._create(model_class, *args, **kwargs)
# Set the appropriate flags based on the user type
if cls.is_student:
user.is_student = True
elif cls.is_parent:
user.is_parent = True
user.save()
return user
class ProgramFactory(DjangoModelFactory):
"""
Factory for creating Program instances.
Attributes:
title (str): The generated program title.
summary (str): The generated summary.
"""
class Meta:
model = Program
title: str = LazyAttribute(lambda x: fake.sentence(nb_words=3))
summary: str = LazyAttribute(lambda x: fake.text())
@classmethod
def _create(cls, model_class: type, *args, **kwargs) -> Program:
"""
Create a Program instance using get_or_create to avoid duplicates.
Args:
model_class (type): The class of the model to create.
Returns:
Program: The created Program instance.
"""
program, created = Program.objects.get_or_create(title=kwargs.get("title"), defaults=kwargs)
return program
class StudentFactory(DjangoModelFactory):
"""
Factory for creating Student instances with associated User and Program.
Attributes:
student (User): The associated User instance.
level (str): The level of the student.
program (Program): The associated Program instance.
"""
class Meta:
model = Student
student: User = SubFactory(UserFactory, is_student=True)
level: str = Iterator([choice[0] for choice in LEVEL])
program: Program = SubFactory(ProgramFactory)
class ParentFactory(DjangoModelFactory):
"""
Factory for creating Parent instances with associated User, Student, and Program.
Attributes:
user (User): The associated User instance.
student (Student): The associated Student instance.
first_name (str): The generated first name.
last_name (str): The generated last name.
phone (str): The generated phone number.
email (str): The generated email.
relation_ship (str): The relationship with the student.
"""
class Meta:
model = Parent
user: User = SubFactory(UserFactory, is_parent=True)
student: Student = SubFactory(StudentFactory)
first_name: str = LazyAttribute(lambda x: fake.first_name())
last_name: str = LazyAttribute(lambda x: fake.last_name())
phone: str = LazyAttribute(lambda x: fake.phone_number())
email: str = LazyAttribute(lambda x: fake.email())
relation_ship: str = Iterator([choice[0] for choice in RELATION_SHIP])
def generate_fake_accounts_data(num_programs: int, num_students: int, num_parents: int) -> None:
"""
Generate fake data for Programs, Students, Parents, and DepartmentHeads.
Args:
num_programs (int): Number of programs to generate.
num_students (int): Number of students to generate.
num_parents (int): Number of parents to generate.
"""
programs: List[Program] = ProgramFactory.create_batch(num_programs)
students: List[Student] = StudentFactory.create_batch(num_students)
parents: List[Parent] = ParentFactory.create_batch(num_parents)
print(f"Created {len(programs)} programs.")
print(f"Created {len(students)} students.")
print(f"Created {len(parents)} parents.")
generate_fake_accounts_data(10, 10, 10)