From 2d1be28a4cf2415db8550d569e7a27e327f071b1 Mon Sep 17 00:00:00 2001 From: WajahatKanju Date: Tue, 6 Feb 2024 22:31:13 +0500 Subject: [PATCH] add django extensions, add faker, add Django model factories for accounts --- accounts/models.py | 430 ++++++++++++------------- config/settings.py | 1 + requirements/local.txt | 17 +- scripts/__init__.py | 0 scripts/generate_fake_accounts_data.py | 165 ++++++++++ 5 files changed, 391 insertions(+), 222 deletions(-) create mode 100644 scripts/__init__.py create mode 100644 scripts/generate_fake_accounts_data.py diff --git a/accounts/models.py b/accounts/models.py index 23e5906..e743065 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,215 +1,215 @@ -from django.db import models -from django.urls import reverse -from django.contrib.auth.models import AbstractUser, UserManager -from django.conf import settings - -from django.db.models import Q -from PIL import Image - -from course.models import Program -from .validators import ASCIIUsernameValidator - - -# LEVEL_COURSE = "Level course" -BACHLOAR_DEGREE = "Bachloar" -MASTER_DEGREE = "Master" - -LEVEL = ( - # (LEVEL_COURSE, "Level course"), - (BACHLOAR_DEGREE, "Bachloar Degree"), - (MASTER_DEGREE, "Master Degree"), -) - -FATHER = "Father" -MOTHER = "Mother" -BROTHER = "Brother" -SISTER = "Sister" -GRAND_MOTHER = "Grand mother" -GRAND_FATHER = "Grand father" -OTHER = "Other" - -RELATION_SHIP = ( - (FATHER, "Father"), - (MOTHER, "Mother"), - (BROTHER, "Brother"), - (SISTER, "Sister"), - (GRAND_MOTHER, "Grand mother"), - (GRAND_FATHER, "Grand father"), - (OTHER, "Other"), -) - - -class CustomUserManager(UserManager): - def search(self, query=None): - queryset = self.get_queryset() - if query is not None: - or_lookup = ( - Q(username__icontains=query) - | Q(first_name__icontains=query) - | Q(last_name__icontains=query) - | Q(email__icontains=query) - ) - queryset = queryset.filter( - or_lookup - ).distinct() # distinct() is often necessary with Q lookups - return queryset - - -GENDERS = (("M", "Male"), ("F", "Female")) - - -class User(AbstractUser): - is_student = models.BooleanField(default=False) - is_lecturer = models.BooleanField(default=False) - is_parent = models.BooleanField(default=False) - is_dep_head = models.BooleanField(default=False) - gender = models.CharField(max_length=1, choices=GENDERS, blank=True, null=True) - phone = models.CharField(max_length=60, blank=True, null=True) - address = models.CharField(max_length=60, blank=True, null=True) - picture = models.ImageField( - upload_to="profile_pictures/%y/%m/%d/", default="default.png", null=True - ) - email = models.EmailField(blank=True, null=True) - - username_validator = ASCIIUsernameValidator() - - objects = CustomUserManager() - - class Meta: - ordering = ("-date_joined",) - - @property - def get_full_name(self): - full_name = self.username - if self.first_name and self.last_name: - full_name = self.first_name + " " + self.last_name - return full_name - - @classmethod - def get_student_count(cls): - return cls.objects.filter(is_student=True).count() - - @classmethod - def get_lecturer_count(cls): - return cls.objects.filter(is_lecturer=True).count() - - @classmethod - def get_superuser_count(cls): - return cls.objects.filter(is_superuser=True).count() - - def __str__(self): - return "{} ({})".format(self.username, self.get_full_name) - - @property - def get_user_role(self): - if self.is_superuser: - role = "Admin" - elif self.is_student: - role = "Student" - elif self.is_lecturer: - role = "Lecturer" - elif self.is_parent: - role = "Parent" - - return role - - def get_picture(self): - try: - return self.picture.url - except: - no_picture = settings.MEDIA_URL + "default.png" - return no_picture - - def get_absolute_url(self): - return reverse("profile_single", kwargs={"id": self.id}) - - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - try: - img = Image.open(self.picture.path) - if img.height > 300 or img.width > 300: - output_size = (300, 300) - img.thumbnail(output_size) - img.save(self.picture.path) - except: - pass - - def delete(self, *args, **kwargs): - if self.picture.url != settings.MEDIA_URL + "default.png": - self.picture.delete() - super().delete(*args, **kwargs) - - -class StudentManager(models.Manager): - def search(self, query=None): - qs = self.get_queryset() - if query is not None: - or_lookup = Q(level__icontains=query) | Q(program__icontains=query) - qs = qs.filter( - or_lookup - ).distinct() # distinct() is often necessary with Q lookups - return qs - - -class Student(models.Model): - student = models.OneToOneField(User, on_delete=models.CASCADE) - # id_number = models.CharField(max_length=20, unique=True, blank=True) - level = models.CharField(max_length=25, choices=LEVEL, null=True) - program = models.ForeignKey(Program, on_delete=models.CASCADE, null=True) - - objects = StudentManager() - - class Meta: - ordering = ("-student__date_joined",) - - def __str__(self): - return self.student.get_full_name - - @classmethod - def get_gender_count(cls): - males_count = Student.objects.filter(student__gender="M").count() - females_count = Student.objects.filter(student__gender="F").count() - - return {"M": males_count, "F": females_count} - - def get_absolute_url(self): - return reverse("profile_single", kwargs={"id": self.id}) - - def delete(self, *args, **kwargs): - self.student.delete() - super().delete(*args, **kwargs) - - -class Parent(models.Model): - """ - Connect student with their parent, parents can - only view their connected students information - """ - - user = models.OneToOneField(User, on_delete=models.CASCADE) - student = models.OneToOneField(Student, null=True, on_delete=models.SET_NULL) - first_name = models.CharField(max_length=120) - last_name = models.CharField(max_length=120) - phone = models.CharField(max_length=60, blank=True, null=True) - email = models.EmailField(blank=True, null=True) - - # What is the relationship between the student and - # the parent (i.e. father, mother, brother, sister) - relation_ship = models.TextField(choices=RELATION_SHIP, blank=True) - - class Meta: - ordering = ("-user__date_joined",) - - def __str__(self): - return self.user.username - - -class DepartmentHead(models.Model): - user = models.OneToOneField(User, on_delete=models.CASCADE) - department = models.ForeignKey(Program, on_delete=models.CASCADE, null=True) - - class Meta: - ordering = ("-user__date_joined",) - - def __str__(self): - return "{}".format(self.user) +from django.db import models +from django.urls import reverse +from django.contrib.auth.models import AbstractUser, UserManager +from django.conf import settings + +from django.db.models import Q +from PIL import Image + +from course.models import Program +from .validators import ASCIIUsernameValidator + + +# LEVEL_COURSE = "Level course" +BACHLOAR_DEGREE = "Bachloar" +MASTER_DEGREE = "Master" + +LEVEL = ( + # (LEVEL_COURSE, "Level course"), + (BACHLOAR_DEGREE, "Bachloar Degree"), + (MASTER_DEGREE, "Master Degree"), +) + +FATHER = "Father" +MOTHER = "Mother" +BROTHER = "Brother" +SISTER = "Sister" +GRAND_MOTHER = "Grand mother" +GRAND_FATHER = "Grand father" +OTHER = "Other" + +RELATION_SHIP = ( + (FATHER, "Father"), + (MOTHER, "Mother"), + (BROTHER, "Brother"), + (SISTER, "Sister"), + (GRAND_MOTHER, "Grand mother"), + (GRAND_FATHER, "Grand father"), + (OTHER, "Other"), +) + + +class CustomUserManager(UserManager): + def search(self, query=None): + queryset = self.get_queryset() + if query is not None: + or_lookup = ( + Q(username__icontains=query) + | Q(first_name__icontains=query) + | Q(last_name__icontains=query) + | Q(email__icontains=query) + ) + queryset = queryset.filter( + or_lookup + ).distinct() # distinct() is often necessary with Q lookups + return queryset + + +GENDERS = (("M", "Male"), ("F", "Female")) + + +class User(AbstractUser): + is_student = models.BooleanField(default=False) + is_lecturer = models.BooleanField(default=False) + is_parent = models.BooleanField(default=False) + is_dep_head = models.BooleanField(default=False) + gender = models.CharField(max_length=1, choices=GENDERS, blank=True, null=True) + phone = models.CharField(max_length=60, blank=True, null=True) + address = models.CharField(max_length=60, blank=True, null=True) + picture = models.ImageField( + upload_to="profile_pictures/%y/%m/%d/", default="default.png", null=True + ) + email = models.EmailField(blank=True, null=True) + + username_validator = ASCIIUsernameValidator() + + objects = CustomUserManager() + + class Meta: + ordering = ("-date_joined",) + + @property + def get_full_name(self): + full_name = self.username + if self.first_name and self.last_name: + full_name = self.first_name + " " + self.last_name + return full_name + + @classmethod + def get_student_count(cls): + return cls.objects.filter(is_student=True).count() + + @classmethod + def get_lecturer_count(cls): + return cls.objects.filter(is_lecturer=True).count() + + @classmethod + def get_superuser_count(cls): + return cls.objects.filter(is_superuser=True).count() + + def __str__(self): + return "{} ({})".format(self.username, self.get_full_name) + + @property + def get_user_role(self): + if self.is_superuser: + role = "Admin" + elif self.is_student: + role = "Student" + elif self.is_lecturer: + role = "Lecturer" + elif self.is_parent: + role = "Parent" + + return role + + def get_picture(self): + try: + return self.picture.url + except: + no_picture = settings.MEDIA_URL + "default.png" + return no_picture + + def get_absolute_url(self): + return reverse("profile_single", kwargs={"id": self.id}) + + def save(self, *args, **kwargs): + super().save(*args, **kwargs) + try: + img = Image.open(self.picture.path) + if img.height > 300 or img.width > 300: + output_size = (300, 300) + img.thumbnail(output_size) + img.save(self.picture.path) + except: + pass + + def delete(self, *args, **kwargs): + if self.picture.url != settings.MEDIA_URL + "default.png": + self.picture.delete() + super().delete(*args, **kwargs) + + +class StudentManager(models.Manager): + def search(self, query=None): + qs = self.get_queryset() + if query is not None: + or_lookup = Q(level__icontains=query) | Q(program__icontains=query) + qs = qs.filter( + or_lookup + ).distinct() # distinct() is often necessary with Q lookups + return qs + + +class Student(models.Model): + student = models.OneToOneField(User, on_delete=models.CASCADE) + # id_number = models.CharField(max_length=20, unique=True, blank=True) + level = models.CharField(max_length=25, choices=LEVEL, null=True) + program = models.ForeignKey(Program, on_delete=models.CASCADE, null=True) + + objects = StudentManager() + + class Meta: + ordering = ("-student__date_joined",) + + def __str__(self): + return self.student.get_full_name + + @classmethod + def get_gender_count(cls): + males_count = Student.objects.filter(student__gender="M").count() + females_count = Student.objects.filter(student__gender="F").count() + + return {"M": males_count, "F": females_count} + + def get_absolute_url(self): + return reverse("profile_single", kwargs={"id": self.id}) + + def delete(self, *args, **kwargs): + self.student.delete() + super().delete(*args, **kwargs) + + +class Parent(models.Model): + """ + Connect student with their parent, parents can + only view their connected students information + """ + + user = models.OneToOneField(User, on_delete=models.CASCADE) + student = models.OneToOneField(Student, null=True, on_delete=models.SET_NULL) + first_name = models.CharField(max_length=120) + last_name = models.CharField(max_length=120) + phone = models.CharField(max_length=60, blank=True, null=True) + email = models.EmailField(blank=True, null=True) + + # What is the relationship between the student and + # the parent (i.e. father, mother, brother, sister) + relation_ship = models.TextField(choices=RELATION_SHIP, blank=True) + + class Meta: + ordering = ("-user__date_joined",) + + def __str__(self): + return self.user.username + + +class DepartmentHead(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + department = models.ForeignKey(Program, on_delete=models.CASCADE, null=True) + + class Meta: + ordering = ("-user__date_joined",) + + def __str__(self): + return "{}".format(self.user) diff --git a/config/settings.py b/config/settings.py index a7f6d17..7f0d409 100644 --- a/config/settings.py +++ b/config/settings.py @@ -37,6 +37,7 @@ AUTH_USER_MODEL = "accounts.User" DJANGO_APPS = [ "jet.dashboard", "jet", + "django_extensions", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", diff --git a/requirements/local.txt b/requirements/local.txt index f7a7141..23de88f 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -1,7 +1,10 @@ --r base.txt - -psycopg2-binary==2.9.5 # https://github.com/psycopg/psycopg2 - -# Code quality -# ------------------------------------------------------------------------------ -black==22.12.0 # https://github.com/psf/black \ No newline at end of file +-r base.txt + +psycopg2-binary==2.9.5 # https://github.com/psycopg/psycopg2 + +# Code quality +# ------------------------------------------------------------------------------ +black==22.12.0 # https://github.com/psf/black + +# Django Extensions +django-extensions==3.1.3 # https://github.com/django-extensions/django-extensions \ No newline at end of file diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/scripts/generate_fake_accounts_data.py b/scripts/generate_fake_accounts_data.py new file mode 100644 index 0000000..b812a03 --- /dev/null +++ b/scripts/generate_fake_accounts_data.py @@ -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) \ No newline at end of file