diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..600d2d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode \ No newline at end of file diff --git a/employee_visualizer/data-devclub.json b/employee_visualizer/data-devclub.json new file mode 100644 index 0000000..5379eaf --- /dev/null +++ b/employee_visualizer/data-devclub.json @@ -0,0 +1 @@ +{"records": {"record": [{"EMPID": "2", "PASSPORT": "CDC87ETW8EQ", "FIRSTNAME": "Burton", "LASTNAME": "Gallegos", "GENDER": "0", "BIRTHDAY": "22-09-1960", "NATIONALITY": "Germany", "HIRED": "29-10-2021", "DEPT": "Aircraft Maintenance", "POSITION": "Pilot", "STATUS": "1", "REGION": "APAC"}, {"EMPID": "3", "PASSPORT": "JUI65YBK7AF", "FIRSTNAME": "Jada", "LASTNAME": "Bender", "GENDER": "0", "BIRTHDAY": "28-05-1963", "NATIONALITY": "Pakistan", "HIRED": "11-02-2001", "DEPT": "Pilot", "POSITION": "Pilot", "STATUS": "1", "REGION": "Canada"}, {"EMPID": "5", "PASSPORT": "AZE20CSG4MU", "FIRSTNAME": "Lillian", "LASTNAME": "Reese", "GENDER": "0", "BIRTHDAY": "03-12-1982", "NATIONALITY": "Ukraine", "HIRED": "19-05-2002", "DEPT": "Flight Planning", "POSITION": "Steward", "STATUS": "1", "REGION": "Canada"}, {"EMPID": "23", "PASSPORT": "NFH65BYM0VB", "FIRSTNAME": "Armand", "LASTNAME": "Horn", "GENDER": "0", "BIRTHDAY": "24-05-1987", "NATIONALITY": "Netherlands", "HIRED": "19-06-2007", "DEPT": "Aircraft Maintenance", "POSITION": "Airhostess", "STATUS": "1", "REGION": "Ocenia"}, {"EMPID": "29", "PASSPORT": "CMK62UAD3VK", "FIRSTNAME": "Rowan", "LASTNAME": "Leonard", "GENDER": "1", "BIRTHDAY": "15-07-1974", "NATIONALITY": "Germany", "HIRED": "27-03-2004", "DEPT": "Aircraft Maintenance", "POSITION": "Pilot", "STATUS": "1", "REGION": "Ocenia"}, {"EMPID": "33", "PASSPORT": "EWD45RJW5YK", "FIRSTNAME": "Carter", "LASTNAME": "Velasquez", "GENDER": "0", "BIRTHDAY": "23-11-1967", "NATIONALITY": "Indonesia", "HIRED": "27-02-2005", "DEPT": "Flight Planning", "POSITION": "Pilot", "STATUS": "1", "REGION": "APAC"}, {"EMPID": "34", "PASSPORT": "BFS82MEY3CX", "FIRSTNAME": "Selma", "LASTNAME": "Bush", "GENDER": "0", "BIRTHDAY": "26-03-1972", "NATIONALITY": "Italy", "HIRED": "10-10-2008", "DEPT": "Flight Attendance", "POSITION": "Airhostess", "STATUS": "1", "REGION": "USA"}, {"EMPID": "50", "PASSPORT": "MRC33GHJ2KW", "FIRSTNAME": "Calvin", "LASTNAME": "Roach", "GENDER": "1", "BIRTHDAY": "16-04-1999", "NATIONALITY": "Mexico", "HIRED": "18-03-2011", "DEPT": "Flight Attendance", "POSITION": "Steward", "STATUS": "1", "REGION": "Europe"}, {"EMPID": "66", "PASSPORT": "WKV12UQC6QF", "FIRSTNAME": "Zachery", "LASTNAME": "Valentine", "GENDER": "0", "BIRTHDAY": "04-06-1971", "NATIONALITY": "Philippines", "HIRED": "25-08-2011", "DEPT": "Flight Attendance", "POSITION": "Steward", "STATUS": "1", "REGION": "Middle East"}, {"EMPID": "80", "PASSPORT": "EUC74ENE9ZK", "FIRSTNAME": "Ryan", "LASTNAME": "Rush", "GENDER": "0", "BIRTHDAY": "13-06-1998", "NATIONALITY": "Italy", "HIRED": "31-07-2019", "DEPT": "Aircraft Maintenance", "POSITION": "Pilot", "STATUS": "1", "REGION": "Middle East"}, {"EMPID": "93", "PASSPORT": "UXL43IOW6OV", "FIRSTNAME": "Honorato", "LASTNAME": "Maxwell", "GENDER": "1", "BIRTHDAY": "09-03-1982", "NATIONALITY": "France", "HIRED": "04-02-2017", "DEPT": "Aircraft Maintenance", "POSITION": "Airhostess", "STATUS": "1", "REGION": "Europe"}, {"EMPID": "95", "PASSPORT": "OUP31WOE2IE", "FIRSTNAME": "Dara", "LASTNAME": "Wilcox", "GENDER": "1", "BIRTHDAY": "29-06-1996", "NATIONALITY": "Singapore", "HIRED": "18-05-2011", "DEPT": "Flight Attendance", "POSITION": "Airhostess", "STATUS": "1", "REGION": "Canada"}, {"EMPID": "97", "PASSPORT": "SUF73DKV4QE", "FIRSTNAME": "Dante", "LASTNAME": "Hart", "GENDER": "0", "BIRTHDAY": "21-12-1999", "NATIONALITY": "Peru", "HIRED": "22-02-2016", "DEPT": "Pilot", "POSITION": "Pilot", "STATUS": "1", "REGION": "Europe"}]}} \ No newline at end of file diff --git a/employee_visualizer/db.sqlite3 b/employee_visualizer/db.sqlite3 new file mode 100644 index 0000000..e6ab09a Binary files /dev/null and b/employee_visualizer/db.sqlite3 differ diff --git a/employee_visualizer/employee_visualizer/__init__.py b/employee_visualizer/employee_visualizer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/employee_visualizer/employee_visualizer/asgi.py b/employee_visualizer/employee_visualizer/asgi.py new file mode 100644 index 0000000..92acf14 --- /dev/null +++ b/employee_visualizer/employee_visualizer/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for employee_visualizer 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.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'employee_visualizer.settings') + +application = get_asgi_application() diff --git a/employee_visualizer/employee_visualizer/settings.py b/employee_visualizer/employee_visualizer/settings.py new file mode 100644 index 0000000..570f56a --- /dev/null +++ b/employee_visualizer/employee_visualizer/settings.py @@ -0,0 +1,124 @@ +""" +Django settings for employee_visualizer project. + +Generated by 'django-admin startproject' using Django 4.1.2. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/4.1/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure--)5lf(g+l09o5l(3%vfv=bf@r796dopy*v=%tnpxdkg$k8hpip' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'employees' +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'employee_visualizer.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'employee_visualizer.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/4.1/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/4.1/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.1/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/employee_visualizer/employee_visualizer/urls.py b/employee_visualizer/employee_visualizer/urls.py new file mode 100644 index 0000000..90de9b3 --- /dev/null +++ b/employee_visualizer/employee_visualizer/urls.py @@ -0,0 +1,22 @@ +"""employee_visualizer URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/4.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('', include('employees.urls')) +] diff --git a/employee_visualizer/employee_visualizer/wsgi.py b/employee_visualizer/employee_visualizer/wsgi.py new file mode 100644 index 0000000..8409a4e --- /dev/null +++ b/employee_visualizer/employee_visualizer/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for employee_visualizer project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'employee_visualizer.settings') + +application = get_wsgi_application() diff --git a/employee_visualizer/employees/__init__.py b/employee_visualizer/employees/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/employee_visualizer/employees/admin.py b/employee_visualizer/employees/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/employee_visualizer/employees/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/employee_visualizer/employees/apps.py b/employee_visualizer/employees/apps.py new file mode 100644 index 0000000..52b700d --- /dev/null +++ b/employee_visualizer/employees/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EmployeesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'employees' diff --git a/employee_visualizer/employees/migrations/0001_initial.py b/employee_visualizer/employees/migrations/0001_initial.py new file mode 100644 index 0000000..2f70b45 --- /dev/null +++ b/employee_visualizer/employees/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.2 on 2022-10-09 03:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Employee', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('emp_id', models.IntegerField()), + ('passport', models.TextField()), + ('firstname', models.TextField()), + ('lastname', models.TextField()), + ('gender', models.TextField(choices=[('0', 'Male'), ('1', 'Female')])), + ('birthday', models.DateField()), + ('natinality', models.TextField()), + ('hired', models.DateField()), + ('dept', models.TextField()), + ('position', models.TextField()), + ('status', models.TextField(choices=[('1', 'Active'), ('2', 'Resigned'), ('3', 'Retired')])), + ('region', models.TextField()), + ], + ), + ] diff --git a/employee_visualizer/employees/migrations/0002_alter_employee_gender_alter_employee_status.py b/employee_visualizer/employees/migrations/0002_alter_employee_gender_alter_employee_status.py new file mode 100644 index 0000000..4aed486 --- /dev/null +++ b/employee_visualizer/employees/migrations/0002_alter_employee_gender_alter_employee_status.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.2 on 2022-10-09 04:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('employees', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='employee', + name='gender', + field=models.IntegerField(choices=[(0, 'Male'), (1, 'Female')]), + ), + migrations.AlterField( + model_name='employee', + name='status', + field=models.IntegerField(choices=[(1, 'Active'), (2, 'Resigned'), (3, 'Retired')]), + ), + ] diff --git a/employee_visualizer/employees/migrations/0003_rename_natinality_employee_nationality.py b/employee_visualizer/employees/migrations/0003_rename_natinality_employee_nationality.py new file mode 100644 index 0000000..8e8d2ed --- /dev/null +++ b/employee_visualizer/employees/migrations/0003_rename_natinality_employee_nationality.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.2 on 2022-10-09 04:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('employees', '0002_alter_employee_gender_alter_employee_status'), + ] + + operations = [ + migrations.RenameField( + model_name='employee', + old_name='natinality', + new_name='nationality', + ), + ] diff --git a/employee_visualizer/employees/migrations/__init__.py b/employee_visualizer/employees/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/employee_visualizer/employees/models.py b/employee_visualizer/employees/models.py new file mode 100644 index 0000000..914f58b --- /dev/null +++ b/employee_visualizer/employees/models.py @@ -0,0 +1,27 @@ +from django.db import models + +class Employee(models.Model): + class Gender(models.IntegerChoices): + MALE = 0 + FEMALE = 1 + + class Status(models.IntegerChoices): + ACTIVE = 1 + RESIGNED = 2 + RETIRED = 3 + + emp_id = models.IntegerField() + passport = models.TextField() + firstname = models.TextField() + lastname = models.TextField() + gender = models.IntegerField(choices=Gender.choices) + birthday = models.DateField() + nationality = models.TextField() + hired = models.DateField() + dept = models.TextField() + position = models.TextField() + status = models.IntegerField(choices=Status.choices) + region = models.TextField() + + def __str__(self): + return self.emp_id \ No newline at end of file diff --git a/employee_visualizer/employees/templates/index.html b/employee_visualizer/employees/templates/index.html new file mode 100644 index 0000000..2d784c2 --- /dev/null +++ b/employee_visualizer/employees/templates/index.html @@ -0,0 +1,151 @@ + + + + + + + Employee + + + + + + + + + + +
+

Employee

+ +
+
+
+
+
Job Position
+ +
+
+
+
+
+
+
Hired
+ +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
IDEmployeeIDPassportFirstnameLastnameGenderDath of BirthNationalityHired DateDeptPositionStatusRegion
+
+ + + + + + + + \ No newline at end of file diff --git a/employee_visualizer/employees/tests.py b/employee_visualizer/employees/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/employee_visualizer/employees/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/employee_visualizer/employees/urls.py b/employee_visualizer/employees/urls.py new file mode 100644 index 0000000..79e73eb --- /dev/null +++ b/employee_visualizer/employees/urls.py @@ -0,0 +1,8 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.index, name='index'), + path('fetch/', views.get_employees, name='get_employees'), + path('load_init_employees_trigger/', views.load_init_employees_trigger, name='load_init_employees_trigger') +] \ No newline at end of file diff --git a/employee_visualizer/employees/views.py b/employee_visualizer/employees/views.py new file mode 100644 index 0000000..6940119 --- /dev/null +++ b/employee_visualizer/employees/views.py @@ -0,0 +1,135 @@ +from django.shortcuts import render +from django.http import HttpResponse, JsonResponse +from django.core import serializers +from .models import Employee +from datetime import datetime + +import os +import xmltodict +import json + +# *************** chart stat *************** +def stat_group_job_position(): + labels = [] + data = [] + + object_list = Employee.objects.all() + employees = serializers.serialize('python', object_list) + for employee in employees: + position = employee['fields']['position'] + if position not in labels: + labels.append(position) + + for label in labels: + data.append(sum(map(lambda emp : emp['fields']['position'] == label, employees))) + + return { + 'labels': labels, + 'data': data + } + +def stat_group_hired_by_gender(collecting_history_year): + years = [] + males = [] + females = [] + + object_list = Employee.objects.all() + employees = serializers.serialize('python', object_list) + # get last year of employee hired + print([employee['fields']['hired'] for employee in employees]) + last_emp_hired_year = max(dt for dt in [employee['fields']['hired'] for employee in employees]).year + last_emp_hired_year = last_emp_hired_year + 1 + for year in range(last_emp_hired_year - collecting_history_year, last_emp_hired_year): + years.append(year) + males.append(sum(map(lambda emp : emp['fields']['gender'] == 0 and emp['fields']['hired'].year == year, employees))) + females.append(sum(map(lambda emp: emp['fields']['gender'] == 1 and emp['fields']['hired'].year == year, employees))) + + return { + 'years': years, + 'males': males, + 'females': females + } + +# *************** index *************** +def index(request): + context = { + 'stat_group_job_position': stat_group_job_position(), + 'stat_group_hired_by_gender': stat_group_hired_by_gender(10) + } + return render(request, 'index.html', context) + +def get_employees(request): + object_list = Employee.objects.all() + employees = serializers.serialize('python', object_list) + for employee in employees: + if employee['fields']['gender'] == 0: + employee['fields']['gender'] = 'Male' + else: + employee['fields']['gender'] = 'Female' + + if employee['fields']['status'] == 1: + employee['fields']['status'] = 'Active' + + return JsonResponse(employees, safe=False) + + + + + +# ********** load data from file *********** +def clean_data(data_dict): + temp_dict = {} + pristine_records = [] + + for index in range(len(data_dict['records']['record'])): + record = data_dict['records']['record'][index] + emp_id = record['EMPID'] + passport_no = record['PASSPORT'] + if emp_id in temp_dict and passport_no in temp_dict[emp_id]: + print('found duplicate on employee id: {} and passport no: {} > ignore'.format(emp_id, passport_no)) + elif 'GENDER' in record and record['GENDER'] not in ['0', '1']: + print('wrong gender type ({}) > ignore'.format(record['GENDER'])) + elif 'STATUS' in record and record['STATUS'] not in ['1', '2', '3']: + print('wrong status type ({}) > ignore'.format(record['STATUS'])) + elif 'STATUS' in record and record['STATUS'] != '1': + print('employee status not active ({}) > ignore'.format(record['STATUS'])) + else: + if emp_id not in temp_dict: + temp_dict[emp_id] = {} + temp_dict[emp_id][passport_no] = 'checked' + pristine_records.append(record) + + data_dict['records']['record'] = pristine_records + return data_dict + +def load_init_employees_trigger(request): + script_path = os.path.realpath(os.path.dirname(__file__)) + with open('{}/../../data-devclub-1.xml'.format(script_path)) as xml_file: + data_devclub_dict = xmltodict.parse(xml_file.read()) + pristine_data = clean_data(data_devclub_dict) + + # write json file + json_data = json.dumps(pristine_data) + with open('{}/../data-devclub.json'.format(script_path), 'w') as json_file: + json_file.write(json_data) + + Employee.objects.all().delete() + employees = pristine_data['records']['record'] + for employee in employees: + record = Employee( + emp_id = employee['EMPID'], + passport = employee['PASSPORT'], + firstname = employee['FIRSTNAME'], + lastname = employee['LASTNAME'], + gender = employee['GENDER'], + birthday = datetime.strptime(employee['BIRTHDAY'], '%d-%m-%Y'), + nationality = employee['NATIONALITY'], + hired = datetime.strptime(employee['HIRED'], '%d-%m-%Y'), + dept = employee['DEPT'], + position = employee['POSITION'], + status = employee['STATUS'], + region = employee['REGION'] + ) + record.save() + + return HttpResponse('Data clean and loaded') diff --git a/employee_visualizer/manage.py b/employee_visualizer/manage.py new file mode 100755 index 0000000..7ec52c6 --- /dev/null +++ b/employee_visualizer/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'employee_visualizer.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main()