diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f65216d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "python.analysis.extraPaths": [ + "./service/api" + ] +} \ No newline at end of file diff --git a/ghi/app/src/App.js b/ghi/app/src/App.js index 6fc612d..a99b52c 100644 --- a/ghi/app/src/App.js +++ b/ghi/app/src/App.js @@ -1,6 +1,10 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; import MainPage from './MainPage'; import Nav from './Nav'; +import ModelList from './ModelList'; +import ModelForm from './ModelForm'; +import AutomobileForm from './AutomobileForm'; +import AutomobileList from './AutomobileList'; function App() { return ( @@ -9,6 +13,14 @@ function App() {
} /> + + }/> + }/> + + + }/> + } /> +
diff --git a/ghi/app/src/AutomobileForm.js b/ghi/app/src/AutomobileForm.js new file mode 100644 index 0000000..5370e36 --- /dev/null +++ b/ghi/app/src/AutomobileForm.js @@ -0,0 +1,128 @@ +import { useState, useEffect } from "react"; + +function AutomobileForm() { + const [models, setModels] = useState([]); + + const fetchData = async () => { + const modelUrl = 'http://localhost:8100/api/models/'; + const response = await fetch(modelUrl); + if (response.ok) { + const data = await response.json(); + setModels(data.models) + } + } + + const [color, setColor] = useState(''); + const [year, setYear] = useState(''); + const [vin, setVin] = useState(''); + const [model, setModel] = useState(''); + + const handleColor = (event) => { + setColor(event.target.value); + } + const handleYear = (event) => { + setYear(event.target.value); + } + const handleVin = (event) => { + setVin(event.target.value); + } + const handleModelChange = (event) => { + setModel(event.target.value); + } + + const handleSubmit = async (event) => { + event.preventDefault(); + + let data = {}; + + data.color = color; + data.year = year; + data.vin = vin; + data.model_id = model; + + const url = 'http://localhost:8100/api/automobiles/'; + const fetchConfig = { + method: "post", + body: JSON.stringify(data), + Headers: { + 'Content-Type': 'application/json', + }, + } + + const response = await fetch(url, fetchConfig); + if (response.ok) { + setModels([]); + setColor(''); + setYear(''); + setVin(''); + } + } + + useEffect(() => { + fetchData(); + }, []); + + + return ( +
+
+
+

Add a new vehicle

+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+
+ ); +} + +export default AutomobileForm; \ No newline at end of file diff --git a/ghi/app/src/AutomobileList.js b/ghi/app/src/AutomobileList.js new file mode 100644 index 0000000..c273f71 --- /dev/null +++ b/ghi/app/src/AutomobileList.js @@ -0,0 +1,151 @@ +import React from 'react'; + + +class AutomobileList extends React.Component { + constructor(props) { + super(props) + this.state = { + autos: [] + } + } + + async componentDidMount() { + // const url = "http://localhost:8100/api/automobiles/" + const url = "http://localhost:8100/api/automobiles/" + const response = await fetch(url) + if (response.ok) { + const data = await response.json(); + this.setState({ + autos: data.autos + }) + } + } + + render() { + return ( +
+

Automobiles

+ + + + + + + + + + + + {this.state.autos.map(auto => { + return ( + + + + + + + + ) + })} + +
VINColorYearModelManufacturer
{auto.vin}{auto.color}{auto.year}{auto.model.name}{auto.model.manufacturer.name}
+
+ ) + } +} + export default AutomobileList; + +// import React from "react"; + +// class AutomobileList extends React.Component { +// constructor (props) { +// super(props) +// this.state = { +// autos: [] +// } +// } + +// async componentDinMount() { +// const url = 'http://localhost:8100/api/automobiles/'; +// const response = await fetch(url); +// console.log(response) +// if (response.ok) { +// const data = await response.json(); +// console.log(data); +// this.setState({ +// autos: data.autos +// }) +// } +// } + +// render() { +// return ( +//
+//

Automobile List

+// +// +// +// +// +// +// +// +// +// +// +// {this.state.autos.map(auto => { +// console.log(auto) +// return ( +// +// +// +// +// +// +// +// ) +// })} +// +//
VINColorYearModelManufacturer
{auto.vin}{auto.color}{auto.year}{auto.model.name}{auto.model.manufacturer.name}
+//
+// ) +// } +// } + +// export default AutomobileList; + +// function AutomobileList(props) { +// if (!props.autos || !Array.isArray(props.autos)) { +// return null; +// } +// return ( +// <> +// +// +// +// +// +// +// +// +// +// +// +// {props.autos.map(auto => { +// return ( +// +// +// +// +// +// +// +// ); +// })} +// +//
VINColorYearModelManufacturer
{auto.vin}{auto.color}{auto.year}{auto.model}{auto.manufacturer}
+ +// +// ); +// } + diff --git a/ghi/app/src/ModelForm.js b/ghi/app/src/ModelForm.js new file mode 100644 index 0000000..daec6e8 --- /dev/null +++ b/ghi/app/src/ModelForm.js @@ -0,0 +1,114 @@ +import React, { useEffect, useState } from 'react'; + + +function ModelForm() { + const [manufacturers, setManufacturers] = useState([]); + + const fetchData = async () => { + const url = 'http://localhost:8100/api/manufacturers/'; + const response = await fetch(url); + if (response.ok) { + const data = await response.json(); + setManufacturers(data.manufacturers); + } + } + + const [modelName, setModelName] = useState(''); + + const handleModelName = (event) => { + setModelName(event.target.value); + } + + const [picture_url, setPictureUrl] = useState(''); + + const handlePicture = (event) => { + setPictureUrl(event.target.value); + } + + const [manufacturer, setManufacturerChange] = useState(''); + + const handleManfacturer = (event) => { + setManufacturerChange(event.target.value); + } + + const handleSubmit = async (event) => { + event.preventDefault(); + + let data = {} + + data.name = modelName; + data.picture_url = picture_url; + data.manufacturer_id = manufacturer; + + const modelsUrl = 'http://localhost:8100/api/models/'; + const fetchConfig = { + method: "post", + body: JSON.stringify(data), + Headers: { + 'Content-Type': 'application/json', + }, + } + + const response = await fetch(modelsUrl, fetchConfig); + if (response.ok) { + setManufacturers([]); + setModelName(''); + setPictureUrl(''); + } + } + + useEffect(() => { + fetchData(); + }, []); + + return ( +
+
+
+

Add a new Model

+
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+ ); +} + +export default ModelForm; \ No newline at end of file diff --git a/ghi/app/src/ModelList.js b/ghi/app/src/ModelList.js new file mode 100644 index 0000000..094a744 --- /dev/null +++ b/ghi/app/src/ModelList.js @@ -0,0 +1,55 @@ +import React from "react"; + +class ModelList extends React.Component { + constructor(props) { + super(props) + this.state = { + models: [] + } + } + + async componentDidMount() { + const url = 'http://localhost:8100/api/models/'; + const response = await fetch(url); + if (response.ok) { + const data = await response.json(); + this.setState({ + models: data.models + }) + } + } + + render() { + return ( +
+

Model List

+ + + + + + + + + + + {this.state.models.map(model => { + return ( + + + + + + + ); + })} + +
IDModelManufacturerPicture
{ model.id }{ model.name }{ model.manufacturer.name }
+
+ ); + } + + +} + +export default ModelList; \ No newline at end of file diff --git a/service/api/service_project/settings.py b/service/api/service_project/settings.py index c108355..2bcdce3 100644 --- a/service/api/service_project/settings.py +++ b/service/api/service_project/settings.py @@ -30,6 +30,7 @@ # Application definition INSTALLED_APPS = [ + 'service_rest.apps.ServiceRestConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', diff --git a/service/api/service_project/urls.py b/service/api/service_project/urls.py index be3898f..9d0de7d 100644 --- a/service/api/service_project/urls.py +++ b/service/api/service_project/urls.py @@ -14,8 +14,9 @@ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path +from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), + path('api/', include('service_rest.api_urls')), ] diff --git a/service/api/service_rest/api_urls.py b/service/api/service_rest/api_urls.py new file mode 100644 index 0000000..2602ecc --- /dev/null +++ b/service/api/service_rest/api_urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from .views import api_list_appointments, api_list_technicians, api_show_appointment, api_show_technician, api_list_automobiles + +urlpatterns = [ + path("appointments/", api_list_appointments, name="api_list_appointments"), + path("technicians/", api_list_technicians, name="api_list_technicians"), + path("appointments//", api_show_appointment, + name="api_show_appointment"), + path("technicians//", api_show_technician, name="api_show_technician"), + path("autos/", api_list_automobiles, name="api_list_automobiles"), +] diff --git a/service/api/service_rest/migrations/0001_initial.py b/service/api/service_rest/migrations/0001_initial.py new file mode 100644 index 0000000..dbe73d7 --- /dev/null +++ b/service/api/service_rest/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 4.0.3 on 2023-03-08 03:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='AutomobileVO', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('import_vin', models.CharField(max_length=200, unique=True)), + ('color', models.CharField(max_length=50)), + ('year', models.PositiveSmallIntegerField()), + ], + ), + migrations.CreateModel( + name='Technician', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('employee_number', models.PositiveIntegerField(unique=True)), + ], + ), + migrations.CreateModel( + name='Appointment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('customer_name', models.CharField(max_length=200)), + ('vin', models.CharField(max_length=17)), + ('reason', models.CharField(max_length=200)), + ('date', models.DateTimeField(blank=True, null=True)), + ('time', models.DateTimeField(blank=True, null=True)), + ('is_finished', models.BooleanField(default=False)), + ('is_vip', models.BooleanField(default=False)), + ('technician', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='appointments', to='service_rest.technician')), + ], + ), + ] diff --git a/service/api/service_rest/migrations/0002_alter_appointment_reason.py b/service/api/service_rest/migrations/0002_alter_appointment_reason.py new file mode 100644 index 0000000..6784044 --- /dev/null +++ b/service/api/service_rest/migrations/0002_alter_appointment_reason.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.3 on 2023-03-08 05:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('service_rest', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='appointment', + name='reason', + field=models.CharField(max_length=1000), + ), + ] diff --git a/service/api/service_rest/migrations/0003_automobilevo_import_href_alter_automobilevo_color_and_more.py b/service/api/service_rest/migrations/0003_automobilevo_import_href_alter_automobilevo_color_and_more.py new file mode 100644 index 0000000..ff181a5 --- /dev/null +++ b/service/api/service_rest/migrations/0003_automobilevo_import_href_alter_automobilevo_color_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.0.3 on 2023-03-08 05:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('service_rest', '0002_alter_appointment_reason'), + ] + + operations = [ + migrations.AddField( + model_name='automobilevo', + name='import_href', + field=models.CharField(max_length=200, null=True, unique=True), + ), + migrations.AlterField( + model_name='automobilevo', + name='color', + field=models.CharField(max_length=50, null=True), + ), + migrations.AlterField( + model_name='automobilevo', + name='import_vin', + field=models.CharField(max_length=17, unique=True), + ), + migrations.AlterField( + model_name='automobilevo', + name='year', + field=models.PositiveSmallIntegerField(null=True), + ), + migrations.AlterField( + model_name='technician', + name='name', + field=models.CharField(max_length=200), + ), + ] diff --git a/service/api/service_rest/models.py b/service/api/service_rest/models.py index 71a8362..bd882af 100644 --- a/service/api/service_rest/models.py +++ b/service/api/service_rest/models.py @@ -1,3 +1,85 @@ -from django.db import models +# from django.db import models +# from django.urls import reverse +# # Create your models here. + + +# class AutomobileVO(models.Model): +# import_vin = models.CharField(max_length=200, unique=True) +# color = models.CharField(max_length=50) +# year = models.PositiveSmallIntegerField() + + +# class Technician(models.Model): +# name = models.CharField(max_length=255) +# employee_number = models.PositiveIntegerField(unique=True) + +# def __str__(self): +# return self.name + +# def get_api_url(self): +# return reverse("api_list_technicians", kwargs={"pk": self.id}) + + +# class Appointment(models.Model): +# customer_name = models.CharField(max_length=200) +# vin = models.CharField(max_length=17) +# reason = models.CharField(max_length=200) +# date = models.DateTimeField(null=True, blank=True) +# time = models.DateTimeField(null=True, blank=True) +# is_finished = models.BooleanField(default=False) +# is_vip = models.BooleanField(default=False) + +# technician = models.ForeignKey( +# Technician, +# related_name="appointments", +# on_delete=models.CASCADE, +# ) + +# def get_api_url(self): +# return reverse("api_appointment", kwargs={"pk": self.id}) + +# # def __str__(self): +# # return self.id +from django.db import models +from django.urls import reverse # Create your models here. + + +class AutomobileVO(models.Model): + import_vin = models.CharField(max_length=17, unique=True) + color = models.CharField(max_length=50, null=True) + year = models.PositiveSmallIntegerField(null=True) + import_href = models.CharField(max_length=200, null=True, unique=True) + + +class Technician(models.Model): + name = models.CharField(max_length=200) + employee_number = models.PositiveIntegerField(unique=True) + + def get_api_url(self): + return reverse("api_show_technician", kwargs={"pk": self.id}) + + def __str__(self): + return self.name + + +class Appointment(models.Model): + vin = models.CharField(max_length=17) + customer_name = models.CharField(max_length=200) + date = models.DateTimeField(null=True, blank=True) + time = models.DateTimeField(null=True, blank=True) + technician = models.ForeignKey( + Technician, + related_name="appointments", + on_delete=models.CASCADE + ) + reason = models.CharField(max_length=1000) + is_vip = models.BooleanField(default=False) + is_finished = models.BooleanField(default=False) + + def get_api_url(self): + return reverse("api_list_appointments", kwargs={"pk": self.id}) + + def __str__(self): + return self.vin diff --git a/service/api/service_rest/views.py b/service/api/service_rest/views.py index 91ea44a..5ab6f07 100644 --- a/service/api/service_rest/views.py +++ b/service/api/service_rest/views.py @@ -1,3 +1,273 @@ +# from django.shortcuts import render +# from django.http import JsonResponse +# from django.views.decorators.http import require_http_methods +# from common.json import ModelEncoder +# import json +# from .models import Appointment, AutomobileVO, Technician + + +# # Create your views here. +# class TechnicianDetailEncoder(ModelEncoder): +# model = Technician +# properties = [ +# "id", +# "name", +# "employee_number", +# ] + + +# class AppointmentDetailEncoder(ModelEncoder): +# model = Appointment +# properties = [ +# "id", +# "vin", +# "customer_name", +# "reason", +# "is_finifshed", +# "is_vip", +# ] + +# def get_extra_data(self, o): +# return {"technician": o.technician.name} + + +# class AppointmentListEncoder(ModelEncoder): +# model = Appointment +# properties = [ +# "id", +# "vin", +# "customer_name", +# "reason", +# "is_finifshed", +# "is_vip", +# ] + +# def get_extra_data(self, o): +# return {"technician": o.technician.name} + + +# def api_list_technicians(request): +# pass + + +# @require_http_methods(["GET", "POST"]) +# def api_list_appointments(request): +# if request.method == "GET": +# appointments = Appointment.objects.all() +# return JsonResponse( +# {"appointments": appointments}, +# encoder=AppointmentListEncoder, +# ) + + +# @require_http_methods(["GET", "POST"]) +# def api_appointment(request, id): +# if request.method == "GET": +# try: +# appointment = Appointment.objects.get(pk=id) +# return JsonResponse( +# appointment, +# encoder=AppointmentDetailEncoder, +# safe=False, +# ) +# except Appointment.DoesNotExist: +# return JsonResponse( +# {"message": "Invalid Appoinment ID"}, +# status=400, +# ) + + +# @require_http_methods(["GET", "POST"]) +# def api_list_technicians(request): +# if request.method == "GET": +# technicians = Technician.objects.all() +# return JsonResponse( +# {"technicians": technicians}, +# encoder=TechnicianDetailEncoder, +# ) +# else: # POST +# content = json.loads(request.body) + +# technician = Technician.objects.create(**content) +# return JsonResponse( +# technician, +# encoder=TechnicianDetailEncoder, +# safe=False, +# ) + from django.shortcuts import render +from .models import Technician, Appointment, AutomobileVO +from django.http import JsonResponse +from django.views.decorators.http import require_http_methods +import json +from common.json import ModelEncoder + + +class TechnicianDetailEncoder(ModelEncoder): + model = Technician + properties = [ + "id", + "name", + "employee_number", + ] + + +class AppointmentListEncoder(ModelEncoder): + model = Appointment + properties = [ + "id", + "vin", + "customer_name", + # "date", + # "time", + "reason", + "is_finished", + "is_vip", + ] + + def get_extra_data(self, o): + return {"technician": o.technician.name} + + +class AppointmentDetailEncoder(ModelEncoder): + model = Appointment + properties = [ + "vin", + "customer_name", + # "date", + # "time", + "reason", + "is_finished", + "is_vip", + "technician", + ] + encoders = { + "technician": TechnicianDetailEncoder(), + } + + +@require_http_methods(["GET", "POST"]) +def api_list_appointments(request): + if request.method == "GET": + appointments = Appointment.objects.all() + return JsonResponse( + {"appointments": appointments}, + encoder=AppointmentListEncoder, + ) + else: # POST + content = json.loads(request.body) + + try: + technician_id = content["technician"] + technician = Technician.objects.get(id=technician_id) + content["technician"] = technician + except Technician.DoesNotExist: + return JsonResponse( + {"message": "Invalid technician href"}, + status=400, + ) + + appointment = Appointment.objects.create(**content) + return JsonResponse( + appointment, + encoder=AppointmentDetailEncoder, + safe=False, + ) + + +@require_http_methods(["GET", "PUT", "DELETE"]) +def api_show_appointment(request, id): + if request.method == "GET": + appointment = Appointment.objects.get(id=id) + return JsonResponse( + appointment, + encoder=AppointmentDetailEncoder, + safe=False, + ) + if request.method == "DELETE": + try: + appointment = Appointment.objects.get(id=id) + appointment.delete() + return JsonResponse( + appointment, + encoder=AppointmentDetailEncoder, + safe=False, + ) + except Appointment.DoesNotExist: + return JsonResponse({"message": "Does not exist"}) + + +@require_http_methods(["GET", "POST"]) +def api_list_technicians(request): + if request.method == "GET": + technicians = Technician.objects.all() + return JsonResponse( + {"technicians": technicians}, + encoder=TechnicianDetailEncoder, + ) + else: + content = json.loads(request.body) + + technician = Technician.objects.create(**content) + return JsonResponse( + technician, + encoder=TechnicianDetailEncoder, + safe=False, + ) + + +@require_http_methods(["GET", "POST", "DELETE"]) +def api_show_technician(request, pk): + if request.method == "GET": + technician = Technician.objects.get(id=pk) + return JsonResponse( + technician, + encoder=TechnicianDetailEncoder, + safe=False, + ) + elif request.method == "DELETE": # DELETE + try: + technician = Technician.objects.get(id=pk) + technician.delete() + return JsonResponse( + technician, + encoder=TechnicianDetailEncoder, + safe=False, + ) + except Technician.DoesNotExist: + return JsonResponse({"message": "Does not exist"}) + else: # PUT + try: + content = json.loads(request.body) + technician = Technician.objects.get(id=pk) + + props = ["name"] + for prop in props: + if prop in content: + setattr(technician, prop, content[prop]) + technician.save() + return JsonResponse( + technician, + encoder=TechnicianDetailEncoder, + safe=False + ) + except Technician.DoesNotExist: + response = JsonResponse({"message": "Does not exist"}) + response.status_code = 404 + return response + -# Create your views here. +@require_http_methods(["GET"]) +def api_list_automobiles(request): + if request.method == "GET": + autos = AutomobileVO.objects.all() + auto_list = [] + for auto in autos: + values = { + "import_href": auto.import_href, + "import_vin": auto.import_vin, + "color": auto.color, + "year": auto.year, + } + auto_list.append(values) + return JsonResponse({"autos": auto_list}) diff --git a/service/poll/poller.py b/service/poll/poller.py index 2fc69a0..420dfc5 100644 --- a/service/poll/poller.py +++ b/service/poll/poller.py @@ -11,13 +11,28 @@ # Import models from service_rest, here. # from service_rest.models import Something +from service_rest.models import AutomobileVO + +def get_automobile(): + response = requests.get("http://inventory-api:8000/api/automobiles/") + content = json.loads(response.content) + print(content) + for automobile in content["autos"]: + AutomobileVO.objects.update_or_create( + import_href=automobile["href"], + defaults={ + "import_vin": automobile["vin"], + "color": automobile["color"], + "year": automobile["year"], + } + ) def poll(): while True: print('Service poller polling for data') try: # Write your polling logic, here - pass + get_automobile() except Exception as e: print(e, file=sys.stderr) time.sleep(60)