├── app ├── app │ ├── __init__.py │ ├── urls.py │ ├── env.example │ ├── asgi.py │ ├── wsgi.py │ └── settings.py ├── core │ ├── __init__.py │ ├── test │ │ ├── __init__.py │ │ └── tests.py │ ├── management │ │ ├── __init__.py │ │ └── commands │ │ │ ├── __init__.py │ │ │ ├── startall.py │ │ │ ├── Mogele_Plant_01_Meal.py │ │ │ ├── Mogele_Plant_02_Meal.py │ │ │ ├── Mogele_Plant_01_Att.py │ │ │ └── Mogele_Plant_02_Att.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── admin.py │ ├── apps.py │ ├── urls.py │ ├── serializers.py │ └── views.py ├── .flake8 └── manage.py ├── initialize.text ├── Setup procedure.bat ├── requirements.txt ├── docker-compose.yml ├── Dockerfile ├── LICENSE ├── .gitignore └── README.md /app/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/core/test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/core/management/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/core/management/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/core/models.py: -------------------------------------------------------------------------------- 1 | # from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /app/core/admin.py: -------------------------------------------------------------------------------- 1 | # from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /app/core/test/tests.py: -------------------------------------------------------------------------------- 1 | # from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /app/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | migrations, 4 | __pycache__, 5 | manage.py, 6 | settings.py -------------------------------------------------------------------------------- /initialize.text: -------------------------------------------------------------------------------- 1 | Database size 2 | Jobs file 3 | col update 4 | Queue table 5 | Env queue 6 | Table size add 7 | -------------------------------------------------------------------------------- /Setup procedure.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cmd /k "cd /d C:\xampp\htdocs\zktecon\env\Scripts\scripts & activate & cd /d C:\xampp\htdocs\zktecon\app & python manage.py startall" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django>=3.2.0,<3.2.5 2 | djangorestframework>=3.12.0,<3.12.4 3 | flake8==3.9.2 4 | pyzk>=0.5 5 | requests==2.26.0 6 | django-extensions==3.1.3 7 | tenacity==8.0.1 8 | django_environ==0.8.1 -------------------------------------------------------------------------------- /app/app/urls.py: -------------------------------------------------------------------------------- 1 | 2 | from django.contrib import admin 3 | from django.urls import path, include 4 | 5 | urlpatterns = [ 6 | path('admin/', admin.site.urls), 7 | path('api/', include('core.urls')) 8 | ] 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | ports: 8 | - "8082:8082" 9 | volumes: 10 | - ./app:/app 11 | command: > 12 | sh -c "python manage.py runserver 0.0.0.0:8082" 13 | -------------------------------------------------------------------------------- /app/app/env.example: -------------------------------------------------------------------------------- 1 | PLANT_01_ATT=172.17.32.82 2 | PLANT_01_MEAL_CARD=172.17.32.83 3 | PLANT_02_ATT=172.17.32.82 4 | PLANT_O2_MEAL_CARD=172.17.32.86 5 | ATT_URL="http://localhost:8181/hrmmogllie/api/zkteco/att-new-record" 6 | MEALCARD_URL="http://localhost:8181/hrmmogllie/api/zkteco/mealcard-new-record" -------------------------------------------------------------------------------- /app/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.core.management import call_command 3 | 4 | 5 | class CoreConfig(AppConfig): 6 | default_auto_field = 'django.db.models.BigAutoField' 7 | name = 'core' 8 | 9 | # def ready(self): 10 | # call_command('runscript runserver &') 11 | # call_command('runscript LiveAttendance &') 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-alpine 2 | LABEL MAINTAINER "Bushra M." 3 | 4 | ENV PYTHONUNBUFFERED 1 5 | 6 | EXPOSE 4370 7 | 8 | # Install dependencies 9 | COPY ./requirements.txt /requirements.txt 10 | RUN pip install -r /requirements.txt 11 | 12 | # Setup directory structure 13 | RUN mkdir /app 14 | WORKDIR /app 15 | COPY ./app/ /app 16 | 17 | RUN adduser -D user 18 | USER user -------------------------------------------------------------------------------- /app/app/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for app project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /app/app/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for app project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /app/core/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from django.urls import path 3 | 4 | from rest_framework.urlpatterns import format_suffix_patterns 5 | from . import views 6 | 7 | urlpatterns = [ 8 | 9 | path('user-data', views.user_data), 10 | path('att-list', views.attendance_list), 11 | path('att-live-capture', views.attendance_live_capture), 12 | path('refresh/att/plant/1',views.plant_01_att), 13 | path('refresh/att/plant/2',views.plant_02_att), 14 | path('refresh/meal/plant/1',views.plant_01_meal), 15 | path('refresh/meal/plant/2',views.plant_02_meal) 16 | 17 | ] 18 | 19 | urlpatterns = format_suffix_patterns(urlpatterns) 20 | -------------------------------------------------------------------------------- /app/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'app.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bushra Mustofa Abdri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/core/management/commands/startall.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from subprocess import Popen 4 | from sys import stdout, stdin, stderr 5 | import time 6 | import os 7 | import signal 8 | 9 | 10 | class Command(BaseCommand): 11 | help = 'Run all commands' 12 | commands = [ 13 | 'python manage.py runserver 0.0.0.0:8000', 14 | # 'python manage.py Mogele_Plant_01_Att', 15 | # "python manage.py Mogele_Plant_01_Meal", #uncomment this if you running on Moglle 16 | # 'python manage.py Mogele_Plant_02_Att', 17 | # "python manage.py Mogele_Plant_02_Meal" #uncomment this if you running on Moglle 18 | 19 | ] 20 | 21 | def handle(self, *args, **options): 22 | proc_list = [] 23 | 24 | for command in self.commands: 25 | print("$ " + command) 26 | proc = Popen(command, shell=True, stdin=stdin, 27 | stdout=stdout, stderr=stderr) 28 | proc_list.append(proc) 29 | 30 | try: 31 | while True: 32 | time.sleep(10) 33 | except KeyboardInterrupt: 34 | for proc in proc_list: 35 | os.kill(proc.pid, signal.SIGKILL) 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io 3 | 4 | ### OSX ### 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | 13 | # Thumbnails 14 | ._* 15 | 16 | # Files that might appear on external disk 17 | .Spotlight-V100 18 | .Trashes 19 | 20 | # Directories potentially created on remote AFP share 21 | .AppleDB 22 | .AppleDesktop 23 | Network Trash Folder 24 | Temporary Items 25 | .apdisk 26 | 27 | 28 | ### Python ### 29 | # Byte-compiled / optimized / DLL files 30 | __pycache__/ 31 | *.py[cod] 32 | 33 | # C extensions 34 | *.so 35 | 36 | # Distribution / packaging 37 | .Python 38 | env/ 39 | build/ 40 | develop-eggs/ 41 | dist/ 42 | downloads/ 43 | eggs/ 44 | lib/ 45 | lib64/ 46 | parts/ 47 | sdist/ 48 | var/ 49 | *.egg-info/ 50 | .installed.cfg 51 | *.egg 52 | 53 | # PyInstaller 54 | # Usually these files are written by a python script from a template 55 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 56 | *.manifest 57 | *.spec 58 | 59 | # Installer logs 60 | pip-log.txt 61 | pip-delete-this-directory.txt 62 | 63 | # Unit test / coverage reports 64 | htmlcov/ 65 | .tox/ 66 | .coverage 67 | .cache 68 | nosetests.xml 69 | coverage.xml 70 | 71 | # Translations 72 | *.mo 73 | *.pot 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | 82 | ### Django ### 83 | *.log 84 | *.pot 85 | *.pyc 86 | __pycache__/ 87 | local_settings.py 88 | 89 | .env 90 | db.sqlite3 -------------------------------------------------------------------------------- /app/core/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | 4 | class AttendanceSerializer(serializers.Serializer): 5 | uid = serializers.IntegerField(required=True, max_value=60000) 6 | user_id = serializers.IntegerField(required=True, max_value=60000) 7 | timestamp = serializers.DateTimeField() 8 | # Which is check-in = 0, check-out = 1, Break-out = 2, 9 | # Break-in = 3, Overtime-in = 4 and Overtime-in = 5 10 | punch = serializers.CharField(max_length=100) 11 | # currently if u are check in finger it status =0 or 12 | # if u checked on face status = 15 and pwd status = 3 13 | status = serializers.CharField(max_length=199) 14 | 15 | 16 | class UserSerializer(serializers.Serializer): 17 | uid = serializers.IntegerField(required=True, max_value=60000) 18 | user_id = serializers.IntegerField(required=True, max_value=60000) 19 | name = serializers.CharField(required=True, max_length=199) 20 | # privilege currently 14 for Admin and 0/3 for normal user, 21 | privilege = serializers.CharField(max_length=199) 22 | password = serializers.CharField(max_length=191, allow_blank=True) 23 | group_id = serializers.CharField(required=False, max_length=199) 24 | privilege = serializers.CharField(max_length=199) 25 | 26 | 27 | class AttendanceSerializer(serializers.Serializer): 28 | uid = serializers.IntegerField(required=True, max_value=60000) 29 | user_id = serializers.IntegerField(required=True, max_value=60000) 30 | timestamp = serializers.CharField(required=True, max_length=199) 31 | punch = serializers.CharField(required=True, max_length=199) 32 | status = serializers.CharField(required=True, max_length=199) 33 | -------------------------------------------------------------------------------- /app/core/management/commands/Mogele_Plant_01_Meal.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | from django.core.management.base import BaseCommand 4 | import requests 5 | from core.serializers import AttendanceSerializer 6 | from zk import ZK 7 | import os 8 | import sys 9 | from tenacity import retry, wait_chain, wait_fixed 10 | from django.views.decorators.csrf import csrf_exempt 11 | import environ 12 | # Initialise environment variables 13 | env = environ.Env() 14 | environ.Env.read_env() 15 | 16 | 17 | CWD = os.path.dirname(os.path.realpath(__file__)) 18 | ROOT_DIR = os.path.dirname(CWD) 19 | sys.path.append(ROOT_DIR) 20 | 21 | ip_one = ZK(env('PLANT_01_MEAL_CARD'), port=4370) 22 | # ip_one = ZK('172.16.32.81', port=4370) 23 | conn_one = None 24 | 25 | 26 | class Command(BaseCommand): 27 | # @retry(retry=retry_if_exception_type()) 28 | @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] + 29 | [wait_fixed(7) for i in range(2)] + 30 | [wait_fixed(9)])) 31 | def handle(self, *args, **kwargs): 32 | self.stdout.write("Plant 01 mealcard: Live server is starting") 33 | try: 34 | conn_one = ip_one.connect() 35 | print('------------Plant 01 mealcard: Device connected!!!------------') 36 | for attendance in conn_one.live_capture(): 37 | if attendance is None: 38 | pass 39 | else: 40 | att_data = AttendanceSerializer(attendance) 41 | print(att_data.data) 42 | meal_att_all_list = conn_one.get_attendance() 43 | meal_att_all_list= AttendanceSerializer(meal_att_all_list,many=True) 44 | 45 | payload = {"current_att_data": att_data.data, 46 | "att_all_list": meal_att_all_list.data, 47 | "plant":"1"} 48 | 49 | with open("Meal_payload_plant_01.json", 'w') as jsonfile: 50 | json.dump(payload, jsonfile) 51 | 52 | res = requests.post( 53 | env('MEALCARD_URL'), json=payload) 54 | # if res.status_code != 200: 55 | # conn_one.test_voice(index=33) 56 | # else: 57 | # conn_one.test_voice(index=0) 58 | print("------------Plant 01 mealcard: Punch detected------------") 59 | except UnboundLocalError: 60 | print("Plant 01 mealcard: I am unable to connect to server") 61 | except Exception as e: 62 | print("Plant 01 mealcard: Process terminate : {}".format(e)) 63 | finally: 64 | if conn_one: 65 | conn_one.disconnect() 66 | 67 | -------------------------------------------------------------------------------- /app/core/management/commands/Mogele_Plant_02_Meal.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | from django.core.management.base import BaseCommand 4 | import requests 5 | from core.serializers import AttendanceSerializer 6 | from zk import ZK 7 | import os 8 | import sys 9 | from tenacity import retry, wait_chain, wait_fixed 10 | from django.views.decorators.csrf import csrf_exempt 11 | import environ 12 | # Initialise environment variables 13 | env = environ.Env() 14 | environ.Env.read_env() 15 | 16 | 17 | CWD = os.path.dirname(os.path.realpath(__file__)) 18 | ROOT_DIR = os.path.dirname(CWD) 19 | sys.path.append(ROOT_DIR) 20 | 21 | ip_one = ZK(env('PLANT_O2_MEAL_CARD'), port=4370) 22 | conn_one = None 23 | 24 | 25 | class Command(BaseCommand): 26 | # @retry(retry=retry_if_exception_type()) 27 | @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] + 28 | [wait_fixed(7) for i in range(2)] + 29 | [wait_fixed(9)])) 30 | def handle(self, *args, **kwargs): 31 | self.stdout.write("Plant 02 mealcard: Live server is starting") 32 | try: 33 | conn_one = ip_one.connect() 34 | print('------------Plant 02 mealcard: Device connected!!!------------') 35 | for attendance in conn_one.live_capture(): 36 | if attendance is None: 37 | pass 38 | else: 39 | att_data = AttendanceSerializer(attendance) 40 | print(att_data.data) 41 | meal_att_all_list = conn_one.get_attendance() 42 | meal_att_all_list= AttendanceSerializer(meal_att_all_list,many=True) 43 | 44 | 45 | payload = {"current_att_data": att_data.data, 46 | "att_all_list": meal_att_all_list.data, 47 | "plant":"2"} 48 | 49 | with open("Meal_payload_plant_02.json", 'w') as jsonfile: 50 | json.dump(payload, jsonfile) 51 | res = requests.post( 52 | env("MEALCARD_URL"), json=payload) 53 | print(res.status_code) 54 | # if res.status_code != 200: 55 | # conn_one.test_voice(index=33) 56 | # else: 57 | # conn_one.test_voice(index=0) 58 | print("------------Plant 02 mealcard: Punch detected------------") 59 | except UnboundLocalError: 60 | print(" Plant 02 mealcard: I am unable to connect to server") 61 | except Exception as e: 62 | print("Plant 02 mealcard: Process terminate : {}".format(e)) 63 | finally: 64 | if conn_one: 65 | conn_one.disconnect() 66 | 67 | -------------------------------------------------------------------------------- /app/app/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for app project. 3 | 4 | Generated by 'django-admin startproject' using Django 3.2.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/3.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/3.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | import os 15 | import environ 16 | 17 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 18 | BASE_DIR = Path(__file__).resolve().parent.parent 19 | 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = 'django-insecure-^gp+lno8o1!=ryy$6oqks80k+3^qu^moil(w6=5p7t(!^ov-$%' 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = True 29 | 30 | ALLOWED_HOSTS = ['*'] 31 | 32 | env = environ.Env() 33 | environ.Env.read_env() 34 | 35 | 36 | # Application definition 37 | 38 | INSTALLED_APPS = [ 39 | 'django.contrib.admin', 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | 46 | # custome apps 47 | 'core', 48 | 'rest_framework', 49 | 'django_extensions' 50 | ] 51 | 52 | MIDDLEWARE = [ 53 | 'django.middleware.security.SecurityMiddleware', 54 | 'django.contrib.sessions.middleware.SessionMiddleware', 55 | 'django.middleware.common.CommonMiddleware', 56 | 'django.middleware.csrf.CsrfViewMiddleware', 57 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 58 | 'django.contrib.messages.middleware.MessageMiddleware', 59 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 60 | ] 61 | 62 | ROOT_URLCONF = 'app.urls' 63 | 64 | TEMPLATES = [ 65 | { 66 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 67 | 'DIRS': [], 68 | 'APP_DIRS': True, 69 | 'OPTIONS': { 70 | 'context_processors': [ 71 | 'django.template.context_processors.debug', 72 | 'django.template.context_processors.request', 73 | 'django.contrib.auth.context_processors.auth', 74 | 'django.contrib.messages.context_processors.messages', 75 | ], 76 | }, 77 | }, 78 | ] 79 | 80 | WSGI_APPLICATION = 'app.wsgi.application' 81 | 82 | 83 | # Database 84 | # https://docs.djangoproject.com/en/3.2/ref/settings/#databases 85 | 86 | DATABASES = { 87 | 'default': { 88 | 'ENGINE': 'django.db.backends.sqlite3', 89 | 'NAME': BASE_DIR / 'db.sqlite3', 90 | } 91 | } 92 | 93 | 94 | # Password validation 95 | # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators 96 | 97 | AUTH_PASSWORD_VALIDATORS = [ 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 100 | }, 101 | { 102 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 103 | }, 104 | { 105 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 106 | }, 107 | { 108 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 109 | }, 110 | ] 111 | 112 | 113 | # Internationalization 114 | # https://docs.djangoproject.com/en/3.2/topics/i18n/ 115 | 116 | LANGUAGE_CODE = 'en-us' 117 | 118 | TIME_ZONE = 'UTC' 119 | 120 | USE_I18N = True 121 | 122 | USE_L10N = True 123 | 124 | USE_TZ = True 125 | 126 | 127 | # Static files (CSS, JavaScript, Images) 128 | # https://docs.djangoproject.com/en/3.2/howto/static-files/ 129 | 130 | STATIC_URL = '/static/' 131 | 132 | # Default primary key field type 133 | # https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field 134 | 135 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 136 | -------------------------------------------------------------------------------- /app/core/management/commands/Mogele_Plant_01_Att.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | from django.core.management.base import BaseCommand 4 | import requests 5 | import threading 6 | from core.serializers import AttendanceSerializer,UserSerializer 7 | from zk import ZK 8 | import os 9 | import sys 10 | from tenacity import retry, wait_chain, wait_fixed 11 | import environ 12 | # Initialise environment variables 13 | env = environ.Env() 14 | environ.Env.read_env() 15 | 16 | 17 | CWD = os.path.dirname(os.path.realpath(__file__)) 18 | ROOT_DIR = os.path.dirname(CWD) 19 | sys.path.append(ROOT_DIR) 20 | 21 | ip_one = ZK(env('PLANT_01_ATT'), port=4370) #172.16.32.81 head office ip 22 | conn_one = None 23 | 24 | 25 | 26 | 27 | 28 | class Command(BaseCommand): 29 | # @retry(retry=retry_if_exception_type()) 30 | @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] + 31 | [wait_fixed(7) for i in range(2)] + 32 | [wait_fixed(9)])) 33 | def handle(self, *args, **kwargs): 34 | self.stdout.write("Plant 01 attendance : server running") 35 | try: 36 | conn_one = ip_one.connect() 37 | print('------------Plant 01 attendance : Device connected!!------------') 38 | 39 | for attendance in conn_one.live_capture(): 40 | if attendance is None: 41 | pass 42 | else: 43 | att_data = AttendanceSerializer(attendance) 44 | print(att_data.data) 45 | 46 | strg_att_list = list() 47 | """ 48 | Below codes will updated in next phase 49 | """ 50 | today_date = datetime.now().strftime("%Y-%m-%d") 51 | today_date_split = today_date.split('-') 52 | file_name = 'plant_01_att_'+today_date_split[0]+'_' + \ 53 | today_date_split[1]+'_'+today_date_split[2]+'.json' 54 | 55 | if os.path.exists(file_name): 56 | json_file = open(file_name) 57 | json_string = json_file.read() 58 | json_file.close() 59 | strg_att_list = json.loads(json_string) 60 | strg_att_list.append(att_data.data) 61 | else: 62 | delete_old_files() 63 | att_all_list = conn_one.get_attendance() 64 | att_all_list = AttendanceSerializer( 65 | att_all_list, many=True) 66 | payload = {"current_att_data": att_data.data, 67 | "att_all_list": att_all_list.data} 68 | prev_datas = payload['att_all_list'] 69 | for item in prev_datas: 70 | current_item_date = item['timestamp'] 71 | current_item_split = current_item_date.split('-') 72 | if current_item_split[0] == today_date_split[0] and current_item_split[1] == today_date_split[1]: 73 | strg_att_list.append(item) 74 | strg_att_list.append(att_data.data) 75 | with open(str(file_name), 'w') as jsonfile: 76 | json.dump(strg_att_list, jsonfile) 77 | """ 78 | Above codes will be maintined 79 | """ 80 | 81 | payload = {"current_att_data": att_data.data, 82 | "att_all_list": strg_att_list} 83 | with open('paylodad.json', 'w') as jsonfile: 84 | json.dump(payload,jsonfile) 85 | 86 | 87 | url = env("ATT_URL") 88 | threading.Thread(target=request_task,args=(url,payload)).start() 89 | 90 | print("------------Plant 01 attendance : Punch detected------------") 91 | except UnboundLocalError: 92 | print(" Plant 01 attendance : I am unable to connect to server") 93 | except Exception as e: 94 | print("Plant 01 attendance : Process terminate : {}".format(e)) 95 | finally: 96 | if conn_one: 97 | conn_one.disconnect() 98 | 99 | 100 | def request_task(url,data): 101 | requests.post(url,json = data) 102 | 103 | # Delete old files && Delete existing file 104 | def delete_old_files(): 105 | directory = "." 106 | 107 | files_in_directory = os.listdir(directory) 108 | filtered_files = [file for file in files_in_directory if file.endswith(".json")] 109 | for file in filtered_files: 110 | path_to_file = os.path.join(directory, file) 111 | os.remove(path_to_file) 112 | 113 | 114 | -------------------------------------------------------------------------------- /app/core/management/commands/Mogele_Plant_02_Att.py: -------------------------------------------------------------------------------- 1 | import json 2 | from datetime import datetime 3 | from django.core.management.base import BaseCommand 4 | import requests 5 | import threading 6 | from core.serializers import AttendanceSerializer, UserSerializer 7 | from zk import ZK 8 | import os 9 | import sys 10 | from tenacity import retry, wait_chain, wait_fixed 11 | import environ 12 | # Initialise environment variables 13 | env = environ.Env() 14 | environ.Env.read_env() 15 | 16 | 17 | CWD = os.path.dirname(os.path.realpath(__file__)) 18 | ROOT_DIR = os.path.dirname(CWD) 19 | sys.path.append(ROOT_DIR) 20 | 21 | ip_one = ZK(env('PLANT_02_ATT'), port=4370) 22 | conn_one = None 23 | 24 | 25 | 26 | 27 | 28 | class Command(BaseCommand): 29 | # @retry(retry=retry_if_exception_type()) 30 | @retry(wait=wait_chain(*[wait_fixed(3) for i in range(3)] + 31 | [wait_fixed(7) for i in range(2)] + 32 | [wait_fixed(9)])) 33 | def handle(self, *args, **kwargs): 34 | self.stdout.write("Plant 02 attendance : server running") 35 | try: 36 | conn_one = ip_one.connect() 37 | print('------------Plant 02 attendance : Device connected!!!------------') 38 | 39 | users = conn_one.get_users() 40 | user_list = UserSerializer(users,many=True) 41 | users_payload = { 42 | "All Users: ": user_list.data 43 | } 44 | with open('Plant_02_users.json','w') as jsonfile: 45 | json.dump(users_payload,jsonfile) 46 | 47 | for attendance in conn_one.live_capture(): 48 | if attendance is None: 49 | pass 50 | else: 51 | att_data = AttendanceSerializer(attendance) 52 | print(att_data.data) 53 | 54 | strg_att_list = list() 55 | """ 56 | Below codes will updated in next phase 57 | """ 58 | today_date = datetime.now().strftime("%Y-%m-%d") 59 | today_date_split = today_date.split('-') 60 | file_name = 'plant_02_att_'+today_date_split[0]+'_' + \ 61 | today_date_split[1]+'_'+today_date_split[2]+'.json' 62 | 63 | if os.path.exists(file_name): 64 | json_file = open(file_name) 65 | json_string = json_file.read() 66 | json_file.close() 67 | strg_att_list = json.loads(json_string) 68 | strg_att_list.append(att_data.data) 69 | else: 70 | delete_old_files() 71 | att_all_list = conn_one.get_attendance() 72 | att_all_list = AttendanceSerializer( 73 | att_all_list, many=True) 74 | payload = {"current_att_data": att_data.data, 75 | "att_all_list": att_all_list.data} 76 | prev_datas = payload['att_all_list'] 77 | for item in prev_datas: 78 | current_item_date = item['timestamp'] 79 | current_item_split = current_item_date.split('-') 80 | if current_item_split[0] == today_date_split[0] and current_item_split[1] == today_date_split[1]: 81 | strg_att_list.append(item) 82 | strg_att_list.append(att_data.data) 83 | with open(str(file_name), 'w') as jsonfile: 84 | json.dump(strg_att_list, jsonfile) 85 | """ 86 | Above codes will be maintined 87 | """ 88 | 89 | payload = {"current_att_data": att_data.data, 90 | "att_all_list": strg_att_list} 91 | 92 | 93 | url = env("ATT_URL") 94 | 95 | threading.Thread(target=request_task,args=(url,payload)).start() 96 | 97 | print("------------Plant 02 attendance : Punch detected------------") 98 | except UnboundLocalError: 99 | print(" Plant 02 attendance : I am unable to connect to server") 100 | except Exception as e: 101 | print("Plant 02 attendance : Process terminate : {}".format(e)) 102 | finally: 103 | if conn_one: 104 | conn_one.disconnect() 105 | 106 | def request_task(url,data): 107 | requests.post(url,json = data) 108 | 109 | # Delete old files && Delete existing file 110 | def delete_old_files(): 111 | directory = "." 112 | 113 | files_in_directory = os.listdir(directory) 114 | filtered_files = [file for file in files_in_directory if file.endswith(".json")] 115 | for file in filtered_files: 116 | path_to_file = os.path.join(directory, file) 117 | os.remove(path_to_file) 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZKTeco 2 | 3 | 4 | 5 | ## Getting started 6 | 7 | To make it easy for you to get started with GitLab, here's a list of recommended next steps. 8 | 9 | Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! 10 | 11 | ## Add your files 12 | 13 | - [ ] [Create](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files 14 | - [ ] [Add files using the command line](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: 15 | 16 | ``` 17 | cd existing_repo 18 | git remote add origin https://gitlab.com/bushera.mestofa/zkteco.git 19 | git branch -M main 20 | git push -uf origin main 21 | ``` 22 | 23 | ## Integrate with your tools 24 | 25 | - [ ] [Set up project integrations](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/project/integrations/) 26 | 27 | ## Collaborate with your team 28 | 29 | - [ ] [Invite team members and collaborators](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/project/members/) 30 | - [ ] [Create a new merge request](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) 31 | - [ ] [Automatically close issues from merge requests](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) 32 | - [ ] [Automatically merge when pipeline succeeds](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) 33 | 34 | ## Test and Deploy 35 | 36 | Use the built-in continuous integration in GitLab. 37 | 38 | - [ ] [Get started with GitLab CI/CD](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/ci/quick_start/index.html) 39 | - [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://docs.gitlab.com/ee/user/application_security/sast/) 40 | 41 | *** 42 | 43 | # Editing this README 44 | 45 | When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://gitlab.com/-/experiment/new_project_readme_content:ce94704fc93ff1e49088cc5f1776eca6?https://www.makeareadme.com/) for this template. 46 | 47 | ## Suggestions for a good README 48 | Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. 49 | 50 | ## Name 51 | Choose a self-explaining name for your project. 52 | 53 | ## Description 54 | Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. 55 | 56 | ## Badges 57 | On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. 58 | 59 | ## Visuals 60 | Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. 61 | 62 | ## Installation 63 | Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. 64 | 65 | ## Usage 66 | Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. 67 | 68 | ## Support 69 | Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. 70 | 71 | ## Roadmap 72 | If you have ideas for releases in the future, it is a good idea to list them in the README. 73 | 74 | ## Contributing 75 | State if you are open to contributions and what your requirements are for accepting them. 76 | 77 | For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. 78 | 79 | You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. 80 | 81 | ## Authors and acknowledgment 82 | Show your appreciation to those who have contributed to the project. 83 | 84 | ## License 85 | For open source projects, say how it is licensed. 86 | 87 | ## Project status 88 | If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. 89 | 90 | -------------------------------------------------------------------------------- /app/core/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | from django.http import JsonResponse 3 | from django.http.response import HttpResponse 4 | from .serializers import UserSerializer, AttendanceSerializer 5 | from django.views.decorators.csrf import csrf_exempt 6 | from rest_framework.decorators import api_view 7 | from rest_framework.parsers import JSONParser 8 | import os 9 | import sys 10 | import requests 11 | from datetime import datetime 12 | # import subprocess 13 | 14 | from zk import ZK 15 | 16 | # command = "python manage.py runscript activate_server && python manage.py runscript activate_attendance" 17 | # comm = "git status && python manage.py LiveAttendance" 18 | # subprocess.call(comm, shell=True) 19 | 20 | CWD = os.path.dirname(os.path.realpath(__file__)) 21 | ROOT_DIR = os.path.dirname(CWD) 22 | sys.path.append(ROOT_DIR) 23 | import environ 24 | # Initialise environment variables 25 | env = environ.Env() 26 | environ.Env.read_env() 27 | 28 | conn = None 29 | zk = ZK('172.17.32.83', port=4370) 30 | 31 | 32 | @csrf_exempt 33 | @api_view(['GET', 'PUT']) 34 | def user_data(request): 35 | try: 36 | global conn, zk 37 | conn = zk.connect() 38 | print('Disabling device ...') 39 | conn.disable_device() 40 | print('--- Get User ---') 41 | users = conn.get_users() 42 | print("############### Current total users: ", len(users)) 43 | start = 0 44 | for user in users: 45 | temp = int(user.uid) - start 46 | start = start + 1 47 | if temp != 1: 48 | print("******dirty data at uid: ", user.uid) 49 | except Exception as ex: 50 | print("Process terminate : {}".format(ex)) 51 | return JsonResponse(ex) 52 | 53 | if request.method == 'GET': 54 | serializer = UserSerializer(users, many=True) 55 | return JsonResponse({ 56 | "success": True, 57 | "Data": serializer.data 58 | }, safe=False) 59 | 60 | elif request.method == 'PUT': 61 | # data = JSONParser().parse(request) 62 | d = request.data 63 | serializer = UserSerializer(data=d) 64 | if serializer.is_valid(): 65 | print("----user update started---") 66 | user_data = serializer.data 67 | 68 | try: 69 | user_update = conn.set_user( 70 | uid=user_data['uid'], 71 | name=user_data['name'], 72 | privilege=user_data['privilege'], 73 | password=user_data['password'], 74 | user_id=str(user_data['user_id']) 75 | ) 76 | 77 | except Exception as ex: 78 | return JsonResponse({ 79 | 'success': False, 80 | 'data': ex 81 | }) 82 | raise ex 83 | 84 | return JsonResponse({ 85 | "success": True, 86 | "data": user_data 87 | }) 88 | return JsonResponse({ 89 | "success": False, 90 | 'data': serializer.errors 91 | }) 92 | 93 | 94 | @csrf_exempt 95 | @api_view(['GET']) 96 | def plant_01_att(request): 97 | att_ip_one = ZK(env('PLANT_01_ATT'), port=4370) #172.16.32.81 head office ip 98 | att_conn_one = att_ip_one.connect() 99 | att_all_list = att_conn_one.get_attendance() 100 | att_all_list = AttendanceSerializer( 101 | att_all_list, many=True) 102 | payload = { "att_all_list": att_all_list.data, "plant": '1' } 103 | url = "http://172.17.32.6:8181/hrmmogllie/api/zkteco/sync/att/all" 104 | res = requests.post(url,json = payload) 105 | return JsonResponse({'success':"true"}) 106 | 107 | @csrf_exempt 108 | @api_view(['GET']) 109 | def plant_02_att(request): 110 | att_ip_two = ZK(env('PLANT_02_ATT'), port=4370) #172.16.32.81 head office ip 111 | att_conn_two = att_ip_two.connect() 112 | att_all_list = att_conn_two.get_attendance() 113 | att_all_list = AttendanceSerializer( 114 | att_all_list, many=True) 115 | payload = { "att_all_list": att_all_list.data, "plant": '2' } 116 | url = "http://172.17.32.6:8181/hrmmogllie/api/zkteco/sync/att/all" 117 | res = requests.post(url,json = payload) 118 | return JsonResponse({'success':"true"}) 119 | 120 | @csrf_exempt 121 | @api_view(['GET']) 122 | def plant_01_meal(request): 123 | meal_ip_one = ZK(env('PLANT_01_MEAL_CARD'), port=4370) #172.16.32.81 head office ip 124 | meal_conn_one = meal_ip_one.connect() 125 | att_all_list = meal_conn_one.get_attendance() 126 | att_all_list = AttendanceSerializer( 127 | att_all_list, many=True) 128 | payload = { "att_all_list": att_all_list.data, "plant": '1' } 129 | url = "http://172.17.32.6:8181/hrmmogllie/api/zkteco/sync/meal/all" 130 | res = requests.post(url,json = payload) 131 | return JsonResponse({'success':"true"}) 132 | 133 | @csrf_exempt 134 | @api_view(['GET']) 135 | def plant_02_meal(request): 136 | meal_ip_two = ZK(env('PLANT_O2_MEAL_CARD'), port=4370) #172.16.32.81 head office ip 137 | meal_conn_one = meal_ip_two.connect() 138 | att_all_list = meal_conn_one.get_attendance() 139 | att_all_list = AttendanceSerializer( 140 | att_all_list, many=True) 141 | payload = { "att_all_list": att_all_list.data, "plant": '2' } 142 | url = "http://172.17.32.6:8181/hrmmogllie/api/zkteco/sync/meal/all" 143 | res = requests.post(url,json = payload) 144 | return JsonResponse({'success':"true"}) 145 | 146 | def attendance_list(request): 147 | try: 148 | global conn, zk 149 | conn = zk.connect() 150 | print('Disabling device ...') 151 | conn.disable_device() 152 | print('--- Get attendances ---') 153 | att_list = conn.get_attendance() 154 | att_list = AttendanceSerializer( 155 | att_list, many=True) 156 | 157 | 158 | except Exception as ex: 159 | print("Process terminate : {}".format(ex)) 160 | return JsonResponse(ex) 161 | 162 | if request.method == 'GET': 163 | return JsonResponse(att_list.data, safe=False) 164 | 165 | 166 | def attendance_live_capture(request): 167 | try: 168 | conn = zk.connect() 169 | for attendance in conn.live_capture(): 170 | if attendance is None: 171 | pass 172 | else: 173 | att_data = AttendanceSerializer(attendance) 174 | print(att_data.data) 175 | att_all_list = conn.get_attendance() 176 | payload = {"current_att_data": att_all_list, 177 | "att_all_list": att_data} 178 | res = requests.post( 179 | "http://localhost/hrm/api/zkteco/att-new-record", params=payload) 180 | print(res.json()) 181 | except UnboundLocalError: 182 | print(" I am unable to connect to the server") 183 | except Exception as e: 184 | print("Process terminate : {}".format(e)) 185 | finally: 186 | pass 187 | 188 | @csrf_exempt 189 | def classify_data(att_items): 190 | strg_att_list = list() 191 | 192 | today_date = datetime.now().strftime("%Y-%m-%d") 193 | today_date_split = today_date.split('-') 194 | file_name = today_date_split[0]+'_' + \ 195 | today_date_split[1]+'_'+today_date_split[2]+'.json' 196 | 197 | att_list = att_items 198 | current_data = att_list['current_att_data'] 199 | prev_datas = att_list['att_all_list'] 200 | 201 | if os.path.exists(file_name): 202 | json_file = open(file_name) 203 | json_string = json_file.read() 204 | json_file.close() 205 | strg_att_list = json.loads(json_string) 206 | strg_att_list.append(current_data) 207 | else: 208 | for item in prev_datas: 209 | current_item_date = item['timestamp'] 210 | current_item_split = current_item_date.split('-') 211 | if current_item_split[0] == today_date_split[0] and current_item_split[1] == today_date_split[1]: 212 | strg_att_list.append(item) 213 | strg_att_list.append(current_data) 214 | with open(str(file_name), 'w') as jsonfile: 215 | json.dump(strg_att_list, jsonfile) 216 | 217 | return strg_att_list 218 | --------------------------------------------------------------------------------