--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Elevator System #
2 |
3 | !(https://github.com/SolomonNewCentury/Elevator)
4 |
5 | A elevator system which goes up, down and stops built with Django(Django Rest Framework including Viewsets, Serializers and etc.)
6 |
7 | ## Architecture ##
8 | - When a user logs in, the frontend downloads the elevator list set already. In first case, there will be no elevator set in elevator list
9 | - So, users can set the number of elevators by clicking format button bellow login button. Then this elevator system is initialized.
10 | While using this system, users can initialize system like this.
11 | - After that, elevators are displayed in interface.
12 | - Each elevator is divided into two status: opened or closed
13 | - Each elevator contains follow things:
14 | . floor that elevator is located
15 | . floor to go up or down based on request of user
16 | . status to mark whether elevator is in maintenance or not
17 | . status to mark whether elevator is operational or not
18 | . status to mark whether elevator has to go up or down
19 | . status to mark whether elevator is running or not
20 | - Users can change all of these things by clicking Edit button
21 | - Users can use only elevator that is operational and not in maintenance, not in running and set clear destination
22 |
23 |
24 | ### Requests ###
25 |
26 |
27 | **update 04/06/19**
28 |
29 | - using pipenv for package management
30 | - move to Channels 2
31 | - use redis as the channel layer backing store. for more information, please check [channels_redis](https://github.com/django/channels_redis)
32 |
33 | ### Database ###
34 | For this system, I have used postgreSQL.
35 | In using of this, you has to consider about version of postgreSQL and django. I used django 5.0 and postgreSQL 16.1
36 |
37 | ## Assumptions ##
38 |
39 | Because of time constraints this project lacks of:
40 |
41 | - User Sign-Up / Forgot Password
42 | - Good Test Coverage
43 | - Better Comments / Documentation Strings
44 | - Frontend & Backend Tests
45 | - Modern Frontend Framework (like React)
46 | - Proper UX / UI design (looks plain bootstrap)
47 |
48 | ## Run ##
49 |
50 | 0. Download and Install latest version of python
51 |
52 | 1. Create and activate a virtualenv (Python 3)
53 | ```bash
54 | pipenv --python 3 shell
55 | ```
56 | 2. Install requirements
57 | ```bash
58 | pipenv install
59 | ```
60 | 3. Create a MySQL database
61 | ```sql
62 | CREATE DATABASE chat CHARACTER SET utf8;
63 | ```
64 | 4. Start Redis Server
65 | ```bash
66 | redis-server
67 | ```
68 |
69 | 5. Init database
70 | ```bash
71 | ./manage.py migrate
72 | ```
73 | 6. Run tests
74 | ```bash
75 | ./manage.py test
76 | ```
77 |
78 | 7. Create admin user
79 | ```bash
80 | ./manage.py createsuperuser
81 | ```
82 |
83 | 8. Run development server
84 | ```bash
85 | ./manage.py runserver
86 |
--------------------------------------------------------------------------------
/elevators/views.py:
--------------------------------------------------------------------------------
1 | # elevators/views.py
2 | from rest_framework import viewsets
3 | from .models import Elevator, Request
4 | from .serializers import ElevatorSerializer, RequestSerializer
5 | from django.http import HttpResponse
6 |
7 | from datetime import datetime
8 |
9 | from django.db import connection
10 |
11 | class ElevatorViewSet(viewsets.ModelViewSet):
12 | queryset = Elevator.objects.all()
13 | serializer_class = ElevatorSerializer
14 |
15 | class RequestViewSet(viewsets.ModelViewSet):
16 | queryset = Request.objects.all()
17 | serializer_class = RequestSerializer
18 |
19 | # Function to init elevator system
20 | def init_elevator_system(request):
21 | objects = Elevator.objects.all()
22 | # delete elevators set earlier
23 | objects.delete()
24 | # create new elevators according to the requested number of elevator
25 | for i in range(int(request.POST.get('num'))):
26 | newObjects = Elevator()
27 | newObjects.save()
28 | return HttpResponse('ok')
29 |
30 | # Function to open & close the door according to the requested id of elevator
31 | def door_control(request):
32 | id = int(request.POST.get('id'))
33 | with connection.cursor() as cursor:
34 | cursor.execute('UPDATE "elevators_elevator" SET "door_open" = %s WHERE id = %s', [request.POST.get('type'), id])
35 | return HttpResponse('ok')
36 |
37 | # Function to run given elevator
38 | def run_system(request):
39 | id = int(request.POST.get('id'))
40 | with connection.cursor() as cursor:
41 | cursor.execute('UPDATE "elevators_elevator" SET "moving_up" = %s, "current_floor" = %s WHERE id = %s', [request.POST.get('moving_up'), int(request.POST.get("current_floor")), id])
42 | return HttpResponse('ok')
43 |
44 | # Fucntion to update the status of given elevator
45 | def edit_system(request):
46 | id = int(request.POST.get("id"))
47 | with connection.cursor() as cursor:
48 | cursor.execute('UPDATE "elevators_elevator" SET "in_maintenance" = %s, "operational" = %s WHERE id = %s', [request.POST.get('in_maintenance'), request.POST.get("operational"), id])
49 | # Decide to go up or down to which floor
50 | if (int(request.POST.get("to_floor")) != 0):
51 | if (request.POST.get("edit_type") == 'true'):
52 | objects = Request.objects.get(elevator_id = id)
53 | objects.requested_floor = int(request.POST.get('to_floor'))
54 | objects.save()
55 | elif (request.POST.get("edit_type") == 'false'):
56 | objects = Request(
57 | requested_floor = int(request.POST.get('to_floor')), elevator_id = id,
58 | created_at = datetime.now()
59 | )
60 | objects.save()
61 | return HttpResponse('ok')
62 |
--------------------------------------------------------------------------------
/elevator_system/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for elevator_system project.
3 |
4 | Generated by 'django-admin startproject' using Django 5.0.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/5.0/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/5.0/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 | import os
15 |
16 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
17 | BASE_DIR = Path(__file__).resolve().parent.parent
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = 'django-insecure-#bo&+drcp=^59i#8%i!1@9g%5k-vq(v--egl_addef228622rx'
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS = []
30 |
31 |
32 | # Application definition
33 |
34 | INSTALLED_APPS = [
35 | 'django.contrib.admin',
36 | 'django.contrib.auth',
37 | 'django.contrib.contenttypes',
38 | 'django.contrib.sessions',
39 | 'django.contrib.messages',
40 | 'django.contrib.staticfiles',
41 |
42 | #apps added by developer
43 | 'elevators',
44 | 'rest_framework',
45 | ]
46 |
47 | MIDDLEWARE = [
48 | 'django.middleware.security.SecurityMiddleware',
49 | 'django.contrib.sessions.middleware.SessionMiddleware',
50 | 'django.middleware.common.CommonMiddleware',
51 | # 'django.middleware.csrf.CsrfViewMiddleware',
52 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
53 | 'django.contrib.messages.middleware.MessageMiddleware',
54 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
55 | ]
56 |
57 | ROOT_URLCONF = 'elevator_system.urls'
58 |
59 | TEMPLATES = [
60 | {
61 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
62 | 'DIRS': [os.path.join(BASE_DIR, 'templates')],
63 | 'APP_DIRS': True,
64 | 'OPTIONS': {
65 | 'context_processors': [
66 | 'django.template.context_processors.debug',
67 | 'django.template.context_processors.request',
68 | 'django.contrib.auth.context_processors.auth',
69 | 'django.contrib.messages.context_processors.messages',
70 | ],
71 | },
72 | },
73 | ]
74 |
75 | WSGI_APPLICATION = 'elevator_system.wsgi.application'
76 |
77 | # Database
78 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
79 |
80 | DATABASES = {
81 | 'default': {
82 | 'ENGINE': 'django.db.backends.postgresql',
83 | 'NAME': 'elevator',
84 | 'USER': 'postgres',
85 | 'PASSWORD': 'passioner',
86 | 'OPTIONS': {
87 | }
88 | }
89 | }
90 |
91 |
92 | # Password validation
93 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
94 |
95 | AUTH_PASSWORD_VALIDATORS = [
96 | {
97 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
98 | },
99 | {
100 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
101 | },
102 | {
103 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
104 | },
105 | {
106 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
107 | },
108 | ]
109 |
110 | REST_FRAMEWORK = {
111 | 'DEFAULT_PERMISSION_CLASSES': [
112 | 'rest_framework.permissions.IsAuthenticated'
113 | ],
114 | 'DEFAULT_PAGINATION_CLASS':
115 | 'rest_framework.pagination.LimitOffsetPagination',
116 | 'PAGE_SIZE': 100
117 | }
118 |
119 | # Internationalization
120 | # https://docs.djangoproject.com/en/5.0/topics/i18n/
121 |
122 | LANGUAGE_CODE = 'en-us'
123 |
124 | TIME_ZONE = 'UTC'
125 |
126 | USE_I18N = True
127 |
128 | USE_TZ = True
129 |
130 |
131 | # Static files (CSS, JavaScript, Images)
132 | # https://docs.djangoproject.com/en/5.0/howto/static-files/
133 |
134 | # Collect static files here
135 | STATIC_ROOT = os.path.join(BASE_DIR, 'run', 'static_root')
136 |
137 | # look for static assets here
138 | STATICFILES_DIRS = [
139 | os.path.join(BASE_DIR, 'static'),
140 | ]
141 |
142 | STATIC_URL = 'static/'
143 |
144 | LOGIN_REDIRECT_URL = '/'
145 | LOGIN_URL = '/login/'
146 |
147 | # Default primary key field type
148 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
149 |
150 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
151 |
--------------------------------------------------------------------------------
/static/css/app.css:
--------------------------------------------------------------------------------
1 | .elevator-app {
2 | margin: 0 10px;
3 | background-color: rgb(225, 227, 235);
4 | height: 510px;
5 | overflow: auto;
6 | }
7 |
8 | #formatApp {
9 | position: absolute;
10 | background-color: white;
11 | border-radius: 5px;
12 | box-shadow: 0 0 4px 3px rgb(218, 215, 215);
13 | width:34%;
14 | height: auto;
15 | left: 33%;
16 | top: 30%;
17 | padding: 0 10px;
18 | display: none;
19 | }
20 |
21 | #updateApp {
22 | position: absolute;
23 | background-color: white;
24 | border-radius: 5px;
25 | box-shadow: 0 0 4px 3px rgb(218, 215, 215);
26 | width:34%;
27 | height: auto;
28 | left: 33%;
29 | top: 25%;
30 | padding: 0 10px;
31 | display: none;
32 | }
33 |
34 | #formatApp > .header {
35 | text-align: center;
36 | border-bottom: 2px solid lightgray;
37 | }
38 |
39 | #formatApp > .body {
40 | text-align: center;
41 | border-bottom: 2px solid lightgray;
42 | padding: 10px 0;
43 | }
44 |
45 | #formatApp > .footer {
46 | display: flex;
47 | justify-content: space-around;
48 | text-align: center;
49 | padding: 10px 0;
50 | }
51 |
52 | #updateApp > .header {
53 | text-align: center;
54 | border-bottom: 2px solid lightgray;
55 | }
56 |
57 | #updateApp > .body {
58 | text-align: center;
59 | border-bottom: 2px solid lightgray;
60 | padding: 10px 0;
61 | }
62 |
63 | #updateApp > .body > .item {
64 | display: flex;
65 | justify-content: space-around;
66 | font-size: 1.4em;
67 | border-bottom: 2px solid rgb(236, 230, 230)
68 | }
69 |
70 | #updateApp > .body > .item:last-child {
71 | border: none !important;
72 | }
73 |
74 | #updateApp > .footer {
75 | display: flex;
76 | justify-content: space-around;
77 | text-align: center;
78 | padding: 10px 0;
79 | }
80 |
81 | .item-right {
82 | width: 15%;
83 | }
84 |
85 | #editToFloor {
86 | width: 100%;
87 | }
88 |
89 | .elevator-app > .app-header {
90 | display: flex;
91 | justify-content: space-between;
92 | padding: 0 15px;
93 | border-bottom: 5px outset rgb(255, 255, 255);
94 | }
95 |
96 | .elevator-app > .app-body {
97 | width: 98%;
98 | min-height: 425px;
99 | margin: 5px 1%;
100 | background-color: rgb(240, 240, 247);
101 | }
102 |
103 | .elevator-app > .app-body > .elevators {
104 | display: flex;
105 | align-items: center;
106 | justify-content: space-around;
107 | padding: 20px 10px;
108 | flex-wrap: wrap;
109 | }
110 |
111 | .elevators > .elevator {
112 | width: 23%;
113 | padding: 0.2% 1%;
114 | border: 1px solid rgb(76, 14, 95);
115 | border-radius: 10px;
116 | height: auto;
117 | background-color: white;
118 | }
119 |
120 | .elevators > .elevator > .elevator-header {
121 | cursor: pointer;
122 | border-bottom: 1px solid blue;
123 | text-align: center;
124 | font-size: 1.7em;
125 | color: rgb(126, 54, 140)
126 | }
127 |
128 | .elevators > .elevator > .elevator-body {
129 | width: 100%;
130 | padding: 3% 5%;
131 |
132 | }
133 |
134 | .elevators > .elevator > .elevator-footer {
135 | border-top: 1px solid blue;
136 | width: 100%;
137 | display: flex;
138 | justify-content: space-between;
139 | padding: 2% 0;
140 | }
141 |
142 | .elevator-body > .elevator-item {
143 | display: flex;
144 | justify-content: space-between;
145 | border-bottom: 1px solid black;
146 | margin: 2px 0
147 | }
148 |
149 | .elevator-body > .elevator-item:last-child {
150 | border: none
151 | }
152 |
153 | .elevator-body > .elevator-item > .elevator-text {
154 | font-size: 1.3em;
155 | }
156 |
157 | .elevator-body > .elevator-item > .item {
158 | font-size: 1.2em;
159 | }
160 |
161 | .elevator-body > .elevator-item > .item-circle {
162 | cursor: pointer;
163 | text-align: center;
164 | color: white;
165 | min-width: 27px;
166 | height: 26px;
167 | border-radius: 100%;
168 | }
169 |
170 | .item-red {
171 | background-color: rgb(232, 21, 21);
172 | }
173 |
174 | .item-red:hover {
175 | background-color: red
176 | }
177 |
178 | .item-blue {
179 | background-color: rgb(62, 68, 181);
180 | }
181 |
182 | .item-blue:hover {
183 | background-color: rgb(59, 59, 234);
184 | }
185 |
186 | .item-default {
187 | background-color: rgb(171, 161, 161);
188 | }
189 |
190 | .item-default:hover {
191 | background-color: gray;
192 | }
193 |
194 | .item-orange {
195 | background-color: rgb(252, 201, 36);
196 | }
197 |
198 | .item-orange:hover {
199 | background-color: rgb(255, 221, 0);
200 | }
201 |
202 | .item-success:hover {
203 | background-color: rgb(43, 135, 206);
204 | }
205 |
206 | .item-success {
207 | background-color: rgb(43, 185, 217);
208 | }
209 |
210 | .item-success:hover {
211 | background-color: rgb(45, 204, 239);
212 | }
213 |
214 | ::-webkit-scrollbar {
215 | width: 8px;
216 | height: 8px;
217 | background-color: rgb(134, 146, 142);
218 | }
219 |
220 | ::-webkit-scrollbar:hover {
221 | background-color: rgb(98, 107, 104);
222 | }
--------------------------------------------------------------------------------
/static/js/app.js:
--------------------------------------------------------------------------------
1 | let elevators = $("#elevators")
2 | let requestedStatus = false //variable to mark whether there is requested floor or not in requests list
3 |
4 | //Operation to get elevators set already when system runs
5 | $(document).ready(() => getElevators())
6 |
7 | /**
8 | * Function to get elevators set now
9 | */
10 | const getElevators = async () => {
11 | let requests = await getReqeusts() //To get requests in request list
12 |
13 | //Api to get elevators set from backend
14 | await $.getJSON('api/elevators/', (data) => {
15 | elevators.children('.elevator').remove() //Delete old elevators
16 |
17 | if (data.results && data.results.length) {
18 | for (let i = 0; i < data.results.length; i++) {
19 | let result = data.results[i]
20 | let id = result.id
21 | let current_floor = result.current_floor
22 | let door_open = result.door_open
23 | let request = requests.results && requests.results.length ? requests.results.filter((req) => req.elevator == id) : ''
24 |
25 | //Variable to decide floor that elevator has to go up or down
26 | let to_go = request && request.length ? request[0].requested_floor : 0
27 |
28 | //Variable to mark whether elevator is in maintenance or not
29 | let maintenanceStatus = result.in_maintenance ?
30 | `
Maintenance:
` :
31 | `
Maintenance:
`
32 |
33 | //Variable to mark whether elevator is operational or not
34 | let operationalStatus = result.operational ?
35 | `
Operational:
` :
36 | `
Operational:
`
37 |
38 | let moving_up = parseInt(current_floor) - parseInt(to_go)
39 |
40 | //Variablt to decide whether elevator has to go up or down
41 | let movingStatus = moving_up != 0 && parseInt(to_go)?
42 | moving_up < 0 ?
43 | `
Moving:
` :
44 | `
Moving:
` :
45 | `
Moving:
46 |
`
47 |
48 | //items for each elevators include current_floor, floor_to_go, maintenance, operational, movingup and two buttons.
49 | const eleItem = door_open ?
50 | `