diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000000..51a3623370 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,28 @@ + FROM python:3.8.2 + + ENV PYTHONBUFFERED 1 + ENV PYTHONWRITEBYTECODE 1 + + RUN apt-get update \ + && apt-get install -y netcat + + ENV APP=/app + + # Change the workdir. + WORKDIR $APP + + # Install the requirements + COPY requirements.txt $APP + + RUN pip3 install -r requirements.txt + + # Copy the rest of the files + COPY . $APP + + EXPOSE 8000 + + RUN chmod +x /app/entrypoint.sh + + ENTRYPOINT ["/app/entrypoint.sh"] + + CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "djangobackend.wsgi"] \ No newline at end of file diff --git a/server/db.sqlite3 b/server/db.sqlite3 new file mode 100644 index 0000000000..eef1c67879 Binary files /dev/null and b/server/db.sqlite3 differ diff --git a/server/deployment.yaml b/server/deployment.yaml new file mode 100644 index 0000000000..1c02b730b4 --- /dev/null +++ b/server/deployment.yaml @@ -0,0 +1,29 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + run: dealership + name: dealership +spec: + replicas: 1 + selector: + matchLabels: + run: dealership + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + run: dealership + spec: + containers: + - image: us.icr.io/gonzalesjtho/dealership:latest + imagePullPolicy: Always + name: dealership + ports: + - containerPort: 8000 + protocol: TCP + restartPolicy: Always \ No newline at end of file diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index b1039e16b8..acebd64f2c 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -1,13 +1,22 @@ from django.contrib import admin -# from .models import related models +from .models import CarMake, CarModel # Register your models here. # CarModelInline class +class CarModelInLine(admin.StackedInline): + model = CarModel # CarModelAdmin class +class CarModelAdmin(admin.ModelAdmin): + list_display = ['name'] # CarMakeAdmin class with CarModelInline +class CarMakeAdmin(admin.ModelAdmin): + inlines = [CarModelInLine] + list_display = ['name'] # Register models here +admin.site.register(CarMake, CarMakeAdmin) +admin.site.register(CarModel, CarModelAdmin) diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index 27d96f4eff..afbfd65019 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,27 +1,78 @@ from django.db import models from django.utils.timezone import now - +import datetime # Create your models here. # Create a Car Make model `class CarMake(models.Model)`: -# - Name -# - Description -# - Any other fields you would like to include in car make model -# - __str__ method to print a car make object - +class CarMake(models.Model): + name = models.CharField(null=False, max_length=20, primary_key=True) + desc = models.TextField() + def __str__(self): + return "Name: " + self.name + "," \ + "Description: " + self.desc # Create a Car Model model `class CarModel(models.Model):`: -# - Many-To-One relationship to Car Make model (One Car Make has many Car Models, using ForeignKey field) -# - Name -# - Dealer id, used to refer a dealer created in cloudant database -# - Type (CharField with a choices argument to provide limited choices such as Sedan, SUV, WAGON, etc.) -# - Year (DateField) -# - Any other fields you would like to include in car model -# - __str__ method to print a car make object - +class CarModel(models.Model): + make = models.ForeignKey(CarMake, on_delete=models.CASCADE) + dealer_id = models.IntegerField(null=False) + name = models.CharField(null=False, max_length=20, primary_key=True) + SUV = 'suv' + SEDAN = 'sedan' + WAGON = 'wagon' + JEEP = 'jeep' + TYPE_CHOICES = [ + (SUV, 'SUV'), + (SEDAN, 'Sedan'), + (WAGON, 'Wagon'), + (JEEP, 'Jeep') + ] + model_type = models.CharField(max_length=5, choices=TYPE_CHOICES) + year = models.IntegerField(default=datetime.date.today().year) + + def __str__(self): + return "Make: " + self.make.name + "," \ + "Name: " + self.name + "," \ + "Year: " + str(self.year) + "," \ + "Type: " + self.model_type + "," \ + "Dealer ID: " + str(self.dealer_id) # Create a plain Python class `CarDealer` to hold dealer data - +class CarDealer: + def __init__(self, address, city, full_name, id, lat, long, short_name, st, zip): + self.address = address + self.city = city + self.full_name = full_name + self.id = id + self.lat = lat + self.long = long + self.short_name = short_name + self.st = st + self.zip = zip + + def __str__(self): + return "Dealer name: " + self.full_name # Create a plain Python class `DealerReview` to hold review data +class DealerReview: + + def __init__(self, dealership, name, purchase, review): + # Required attributes + self.dealership = dealership + self.name = name + self.purchase = purchase + self.review = review + # Optional attributes + self.purchase_date = "" + self.purchase_make = "" + self.purchase_model = "" + self.purchase_year = "" + self.sentiment = "" + self.id = "" + + def __str__(self): + return "Review: " + self.review + + def to_json(self): + return json.dumps(self, default=lambda o: o.__dict__, + sort_keys=True, indent=4) \ No newline at end of file diff --git a/server/djangoapp/restapis.py b/server/djangoapp/restapis.py index b4d13f596a..de53a37ec6 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -1,34 +1,140 @@ import requests import json -# import related models here +from .models import CarDealer, DealerReview from requests.auth import HTTPBasicAuth +from ibm_cloud_sdk_core.authenticators import IAMAuthenticator +from ibm_watson import NaturalLanguageUnderstandingV1 +from ibm_watson.natural_language_understanding_v1 import Features,SentimentOptions +import time + +def analyze_review_sentiments(text): + url = "https://api.us-south.natural-language-understanding.watson.cloud.ibm.com/instances/e0644675-9fce-4a74-aee4-2fc8f745c9ab" + api_key = "ews5G2Jj09NPpW2jSZ6_pxcJqeJnvofrNhOzVJbJ07X1" + authenticator = IAMAuthenticator(api_key) + natural_language_understanding = NaturalLanguageUnderstandingV1(version='2021-08-01',authenticator=authenticator) + natural_language_understanding.set_service_url(url) + response = natural_language_understanding.analyze( text=text+"hello hello hello",features=Features(sentiment=SentimentOptions(targets=[text+"hello hello hello"]))).get_result() + label=json.dumps(response, indent=2) + label = response['sentiment']['document']['label'] + + + return(label) -# Create a `get_request` to make HTTP GET requests -# e.g., response = requests.get(url, params=params, headers={'Content-Type': 'application/json'}, -# auth=HTTPBasicAuth('apikey', api_key)) +def get_dealers_from_cf(url, **kwargs): + results = [] + # Call get_request with a URL parameter + json_result = get_request(url) + if json_result: + # Get the row list in JSON as dealers + dealers=json_result + # For each dealer object + for dealer in dealers: + # Get its content in `doc` object + dealer_doc = dealer["doc"] + # Create a CarDealer object with values in `doc` object + dealer_obj = CarDealer(address=dealer_doc["address"], city=dealer_doc["city"], full_name=dealer_doc["full_name"], + id=dealer_doc["id"], lat=dealer_doc["lat"], long=dealer_doc["long"], + short_name=dealer_doc["short_name"], + st=dealer_doc["st"], zip=dealer_doc["zip"]) + results.append(dealer_obj) -# Create a `post_request` to make HTTP POST requests -# e.g., response = requests.post(url, params=kwargs, json=payload) + return results -# Create a get_dealers_from_cf method to get dealers from a cloud function -# def get_dealers_from_cf(url, **kwargs): -# - Call get_request() with specified arguments -# - Parse JSON results into a CarDealer object list +def get_dealer_by_id_from_cf(url, id): + json_result = get_request(url, id=id) + print(json_result) + if json_result: + dealers = json_result + + + dealer_doc = dealers[0] + dealer_obj = CarDealer( + address=dealer_doc["address"], + city=dealer_doc["city"], + id=dealer_doc["id"], + lat=dealer_doc["lat"], + long=dealer_doc["long"], + full_name=dealer_doc["full_name"], + st=dealer_doc["st"], + zip=dealer_doc["zip"], + short_name=dealer_doc["short_name"]) + return dealer_obj -# Create a get_dealer_reviews_from_cf method to get reviews by dealer id from a cloud function -# def get_dealer_by_id_from_cf(url, dealerId): -# - Call get_request() with specified arguments -# - Parse JSON results into a DealerView object list +def get_dealer_reviews_from_cf(url, **kwargs): + results = [] + id = kwargs.get("id") + if id: + json_result = get_request(url, id=id) + else: + json_result = get_request(url) + + if json_result: + reviews = json_result["data"]["docs"] + for dealer_review in reviews: + review_obj = DealerReview(dealership=dealer_review["dealership"], + name=dealer_review["name"], + purchase=dealer_review["purchase"], + review=dealer_review["review"]) + if "id" in dealer_review: + review_obj.id = dealer_review["id"] + if "purchase_date" in dealer_review: + review_obj.purchase_date = dealer_review["purchase_date"] + if "car_make" in dealer_review: + review_obj.car_make = dealer_review["car_make"] + if "car_model" in dealer_review: + review_obj.car_model = dealer_review["car_model"] + if "car_year" in dealer_review: + review_obj.car_year = dealer_review["car_year"] + + sentiment = analyze_review_sentiments(review_obj.review) + print(sentiment) + review_obj.sentiment = sentiment + results.append(review_obj) -# Create an `analyze_review_sentiments` method to call Watson NLU and analyze text -# def analyze_review_sentiments(text): -# - Call get_request() with specified arguments -# - Get the returned sentiment label such as Positive or Negative + return results + +def get_request(url, **kwargs): + + # If argument contain API KEY + api_key = kwargs.get("api_key") + print("GET from {} ".format(url)) + try: + if api_key: + params = dict() + params["text"] = kwargs["text"] + params["version"] = kwargs["version"] + params["features"] = kwargs["features"] + params["return_analyzed_text"] = kwargs["return_analyzed_text"] + response = requests.get(url, params=params, headers={'Content-Type': 'application/json'}, + auth=HTTPBasicAuth('apikey', api_key)) + else: + # Call get method of requests library with URL and parameters + response = requests.get(url, headers={'Content-Type': 'application/json'}, + params=kwargs) + except: + # If any error occurs + print("Network exception occurred") + + status_code = response.status_code + print("With status {} ".format(status_code)) + json_data = json.loads(response.text) + return json_data + + +def post_request(url, payload, **kwargs): + print(kwargs) + print("POST to {} ".format(url)) + print(payload) + response = requests.post(url, params=kwargs, json=payload) + status_code = response.status_code + print("With status {} ".format(status_code)) + json_data = json.loads(response.text) + return json_data diff --git a/server/djangoapp/templates/djangoapp/about.html b/server/djangoapp/templates/djangoapp/about.html new file mode 100644 index 0000000000..33e1bbf0d3 --- /dev/null +++ b/server/djangoapp/templates/djangoapp/about.html @@ -0,0 +1,91 @@ + + + + + Dealership Review + + + + + + + + + + + + + +
+

+ Welcome to Best Cars dealership. +

+
+ +
+

+ We are home to the best cars in North America. We sell domestic and imported cars at reasonable prices. At Best Cars dealership, you can be sure to get the bang for your buck. We sell all kinds of makes and models of cars including brands like Kia, Voltswagen, Ford, Dodge, Cheverolette and much more. We take pride in being awarded with Top 10 most customer friendly dealerships in the nation! This just shows how eager we are to meet you and get you the perfect car for you. Come in today! +

+ +
+ +
+

+ Our Story +

+
+ +
+

+ Grant Angler founded this company in 1890. Following his death in 1910, his family has kept the business going. It is still family owned to this day. Austin Angler holds ownership of the company now with his brother Martin being head of Marketing & Design. +

+
+ + + + diff --git a/server/djangoapp/templates/djangoapp/add_review.html b/server/djangoapp/templates/djangoapp/add_review.html index 768ddf508c..ca2bec3e1c 100644 --- a/server/djangoapp/templates/djangoapp/add_review.html +++ b/server/djangoapp/templates/djangoapp/add_review.html @@ -10,7 +10,57 @@ - - - - \ No newline at end of file + +
+

Add a review about {{dealer.full_name}}

+
+
+ +
+ + +
+ +
+ + + +
+ +
+ + +
+ +
+ + +
+ +
+ {% csrf_token %} +
+ +
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
+
+ + + + + \ No newline at end of file diff --git a/server/djangoapp/templates/djangoapp/contact.html b/server/djangoapp/templates/djangoapp/contact.html new file mode 100644 index 0000000000..7db1cf4f01 --- /dev/null +++ b/server/djangoapp/templates/djangoapp/contact.html @@ -0,0 +1,67 @@ + + + + + Dealership Review + + + + + + + + + + + + + +
+

+ Contact Us +

    +

    Phone Number: 444-444-4444

    +

    Email Address: bestcardealership@best.com

    +

    Address: 1234 58 Ave Little Rock, Arkansas 65789

    +
+

+
+ + + \ No newline at end of file diff --git a/server/djangoapp/templates/djangoapp/dealer_details.html b/server/djangoapp/templates/djangoapp/dealer_details.html index 25bd9a223d..2bcabbc9ee 100644 --- a/server/djangoapp/templates/djangoapp/dealer_details.html +++ b/server/djangoapp/templates/djangoapp/dealer_details.html @@ -7,11 +7,68 @@ - + + + +

Reviews for {{dealer.full_name}}

- - - +
+
+ {% for review in reviews %} +
+
+ {% if review.sentiment == "positive" %} + Sentiment + {% elif review.sentiment == "negative" %} + Sentiment + {% else %} + Sentiment + {% endif %} +
+ {% if review.car_make%} +
{{review.car_make}}, {{review.car_model}}
+
{{review.car_year}}
+ {% endif %} +

{{review.review}}

+
+
+
+ {% endfor %} +
+
+ + diff --git a/server/djangoapp/templates/djangoapp/index.html b/server/djangoapp/templates/djangoapp/index.html index 1a9ee6e39a..1d1b87c812 100644 --- a/server/djangoapp/templates/djangoapp/index.html +++ b/server/djangoapp/templates/djangoapp/index.html @@ -14,12 +14,64 @@ - - This is the index page of your Django app! - + + + + + + + + + + + + + + {% for dealer in dealerships %} + + + + + + + + + {% endfor %} + +
IDDealer NameCityAddressZipState
{{dealer.id}}{{dealer.full_name}}{{dealer.city}}{{dealer.address}}{{dealer.zip}}{{dealer.st}}
+ - - - + diff --git a/server/djangoapp/templates/djangoapp/login_view.html b/server/djangoapp/templates/djangoapp/login_view.html new file mode 100644 index 0000000000..676bbc1f77 --- /dev/null +++ b/server/djangoapp/templates/djangoapp/login_view.html @@ -0,0 +1,28 @@ + + + + + {% load static %} + + + +
+ {% csrf_token %} +
+

Login

+
+ + + + +
+ {% if message %} +
+ {{ message }} +
+ {% endif %} + +
+
+ + \ No newline at end of file diff --git a/server/djangoapp/templates/djangoapp/registration.html b/server/djangoapp/templates/djangoapp/registration.html index ae11ea4b71..6365f078ab 100644 --- a/server/djangoapp/templates/djangoapp/registration.html +++ b/server/djangoapp/templates/djangoapp/registration.html @@ -6,6 +6,29 @@ - +
+
+ {% csrf_token %} +

Sign Up

+
+
+ + + + + + + + + {% if message %} +
+ {{ message }} +
+ {% endif %} +
+ +
+
+
\ No newline at end of file diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 37b1c89d01..5a345b1f1f 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -10,19 +10,19 @@ # name the URL # path for about view - + path(route='about', view=views.about, name='about'), # path for contact us view - + path(route='contact', view=views.contact, name='contact'), # path for registration - + path(route='registration', view=views.registration_request, name='registration'), # path for login - + path(route='login', view=views.login_request, name='login'), # path for logout - + path(route='logout', view=views.logout_request, name='logout'), + # path for dealerships path(route='', view=views.get_dealerships, name='index'), - # path for dealer reviews view - + path(route='dealer//', view=views.get_dealer_details, name='dealer_details'), # path for add a review view - + path(route='dealer//review', view=views.add_review, name='add_review'), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/server/djangoapp/views.py b/server/djangoapp/views.py index 61cc664da0..4d88917eaa 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -2,8 +2,8 @@ from django.http import HttpResponseRedirect, HttpResponse from django.contrib.auth.models import User from django.shortcuts import get_object_or_404, render, redirect -# from .models import related models -# from .restapis import related methods +from .models import CarDealer, DealerReview, CarModel, CarMake +from .restapis import get_dealers_from_cf,get_dealer_reviews_from_cf,post_request, get_dealer_by_id_from_cf, get_request from django.contrib.auth import login, logout, authenticate from django.contrib import messages from datetime import datetime @@ -18,37 +18,125 @@ # Create an `about` view to render a static about page -# def about(request): -# ... +def about(request): + context = {} + if request.method == "GET": + return render(request, 'djangoapp/about.html', context) # Create a `contact` view to return a static contact page #def contact(request): +def contact(request): + context = {} + if request.method == "GET": + return render(request, 'djangoapp/contact.html', context) + +def registration_request(request): + context = {} + if request.method == 'GET': + return render(request, 'djangoapp/registration.html', context) + elif request.method == 'POST': + # Check if user exists + username = request.POST['username'] + password = request.POST['psw'] + first_name = request.POST['firstname'] + last_name = request.POST['lastname'] + user_exist = False + try: + User.objects.get(username=username) + user_exist = True + except: + logger.error("New user") + if not user_exist: + user = User.objects.create_user(username=username, first_name=first_name, last_name=last_name, + password=password) + login(request, user) + return redirect("djangoapp:index") + else: + context['message'] = "User already exists." + return render(request, 'djangoapp/registration.html', context) + -# Create a `login_request` view to handle sign in request -# def login_request(request): -# ... +def login_request(request): + context = {} + if request.method == "POST": + username = request.POST['username'] + password = request.POST['psw'] + user = authenticate(username=username, password=password) + if user is not None: + login(request, user) + return redirect('djangoapp:index') + else: + context['message'] = "Invalid username or password." + return render(request, 'djangoapp/login_view.html', context) + else: + return render(request, 'djangoapp/login_view.html', context) -# Create a `logout_request` view to handle sign out request -# def logout_request(request): -# ... -# Create a `registration_request` view to handle sign up request -# def registration_request(request): -# ... +def logout_request(request): + logout(request) + return redirect('djangoapp:index') # Update the `get_dealerships` view to render the index page with a list of dealerships def get_dealerships(request): - context = {} if request.method == "GET": + url = "https://us-south.functions.appdomain.cloud/api/v1/web/dd273e21-8af8-4608-b838-529759a65be4/dealership-package/get-dealership" + dealerships = get_dealers_from_cf(url) + context = {} + context["dealerships"] = dealerships return render(request, 'djangoapp/index.html', context) - # Create a `get_dealer_details` view to render the reviews of a dealer -# def get_dealer_details(request, dealer_id): -# ... - +def get_dealer_details(request, id): + if request.method == "GET": + context = {} + url = "https://us-south.functions.appdomain.cloud/api/v1/web/dd273e21-8af8-4608-b838-529759a65be4/dealership-package/get-dealership" + dealer = get_dealer_by_id_from_cf(url, id) + context["dealer"] = dealer + + review_url = "https://us-south.functions.appdomain.cloud/api/v1/web/dd273e21-8af8-4608-b838-529759a65be4/dealership-package/get-review" + reviews = get_dealer_reviews_from_cf(review_url, id=id) + print(reviews) + context["reviews"] = reviews + + return render(request, 'djangoapp/dealer_details.html', context) # Create a `add_review` view to submit a review -# def add_review(request, dealer_id): -# ... +def add_review(request, id): + context = {} + dealer_url = "https://us-south.functions.appdomain.cloud/api/v1/web/dd273e21-8af8-4608-b838-529759a65be4/dealership-package/get-dealership" + dealer = get_dealer_by_id_from_cf(dealer_url, id=id) + context["dealer"] = dealer + if request.method == 'GET': + # Get cars for the dealer + cars = CarModel.objects.all() + print(cars) + context["cars"] = cars + + return render(request, 'djangoapp/add_review.html', context) + elif request.method == 'POST': + if request.user.is_authenticated: + username = request.user.username + print(request.POST) + payload = dict() + car_id = request.POST["car"] + car = CarModel.objects.get(pk=car_id) + payload["time"] = datetime.utcnow().isoformat() + payload["name"] = username + payload["dealership"] = id + payload["id"] = id + payload["review"] = request.POST["content"] + payload["purchase"] = False + if "purchasecheck" in request.POST: + if request.POST["purchasecheck"] == 'on': + payload["purchase"] = True + payload["purchase_date"] = request.POST["purchasedate"] + payload["car_make"] = car.make.name + payload["car_model"] = car.name + payload["car_year"] = int(car.year.strftime("%Y")) + + new_payload = {} + new_payload["review"] = payload + review_post_url = "https://us-south.functions.appdomain.cloud/api/v1/web/dd273e21-8af8-4608-b838-529759a65be4/dealership-package/post-reviews" + post_request(review_post_url, new_payload, id=id) + return redirect("djangoapp:dealer_details", id=id) diff --git a/server/entrypoint.sh b/server/entrypoint.sh new file mode 100644 index 0000000000..3d9fc6b4c3 --- /dev/null +++ b/server/entrypoint.sh @@ -0,0 +1,17 @@ + #!/bin/sh + + if [ "$DATABASE" = "postgres" ]; then + echo "Waiting for postgres..." + + while ! nc -z $DATABASE_HOST $DATABASE_PORT; do + sleep 0.1 + done + + echo "PostgreSQL started" + fi + + # Make migrations and migrate the database. + echo "Making migrations and migrating the database. " + python manage.py makemigrations main --noinput + python manage.py migrate --noinput + exec "$@" \ No newline at end of file