diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 05ca50d23b..b8431f1bf6 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -5,6 +5,7 @@ on: branches: [master, main] pull_request: branches: [master, main] + workflow_dispatch: jobs: # list of things to do lint_function_js: diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 0000000000..f79740cfdf --- /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 + +# Set the working directory to /app +ENV APP=/app +WORKDIR $APP + +# Copy the requirements file into the container +COPY requirements.txt $APP + +# Install the requirements +RUN pip3 install -r requirements.txt + +# Copy the rest of the project files into the container +COPY . $APP + +EXPOSE 8000 + +RUN chmod +x /app/entrypoint.sh + +ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"] + +CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "djangobackend.wsgi"] diff --git a/server/db.sqlite3 b/server/db.sqlite3 new file mode 100644 index 0000000000..3b1ddaa920 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..953e6d1eb9 --- /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/sn-labs-marekmarkiew/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..9764fe7f61 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 + extra = 1 -# CarModelAdmin class +# Register your models here. +@admin.register(CarMake) +class CarMakeAdmin(admin.ModelAdmin): + inlines = [CarModelInline] +# CarModelAdmin class +@admin.register(CarModel) +class CarModelAdmin(admin.ModelAdmin): + pass # CarMakeAdmin class with CarModelInline # Register models here diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index 27d96f4eff..6299df7623 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,6 +1,66 @@ -from django.db import models from django.utils.timezone import now +from django.db import models + +class CarMake(models.Model): + name = models.CharField(max_length=100) + description = models.TextField() + + def __str__(self): + return self.name + +class CarModel(models.Model): + make = models.ForeignKey(CarMake, on_delete=models.CASCADE) + dealer_id = models.IntegerField() + name = models.CharField(max_length=100) + + TYPE_CHOICES = ( + ('Sedan', 'Sedan'), + ('SUV', 'SUV'), + ('WAGON', 'WAGON'), + # Add more choices here + ) + + type = models.CharField(max_length=10, choices=TYPE_CHOICES) + year = models.DateField() + + def __str__(self): + return self.name + +class CarDealer(models.Model): + address = models.CharField(max_length=200) + city = models.CharField(max_length=100) + full_name = models.CharField(max_length=100) + id = models.IntegerField(primary_key=True) + lat = models.DecimalField(max_digits=9, decimal_places=6) + long = models.DecimalField(max_digits=9, decimal_places=6) + short_name = models.CharField(max_length=50) + st = models.CharField(max_length=2) + zip = models.CharField(max_length=10) + + def __str__(self): + return "Dealer name: " + self.full_name + +class DealerReview(models.Model): + dealership = models.CharField(max_length=100) + name = models.CharField(max_length=100) + purchase = models.BooleanField() + review = models.TextField() + purchase_date = models.DateField() + car_make = models.CharField(max_length=100) + car_model = models.CharField(max_length=100) + car_year = models.IntegerField() + + SENTIMENT_CHOICES = ( + ('Positive', 'Positive'), + ('Negative', 'Negative'), + ('Neutral', 'Neutral'), + ) + + sentiment = models.CharField(max_length=10, choices=SENTIMENT_CHOICES) + id = models.AutoField(primary_key=True) + def __str__(self): + return self.name # Create your models here. diff --git a/server/djangoapp/restapis.py b/server/djangoapp/restapis.py index b4d13f596a..3bb4518ab9 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -1,13 +1,31 @@ import requests import json # import related models here +from .models import CarDealer, DealerReview from requests.auth import HTTPBasicAuth +WATSON_NLU_API_KEY = 'NgRnMOGD9aZZbFEGJI9B_6EoOc4lYsHbjHXRPEOcKAyx' # 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_request(url, api_key=None, **kwargs): + print(kwargs) + print("GET from {} ".format(url)) + try: + if api_key: + # Call GET method with basic authentication + response = requests.get(url, headers={'Content-Type': 'application/json'}, params=kwargs, auth=HTTPBasicAuth('apikey', api_key)) + else: + # Call GET method without authentication + 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 # Create a `post_request` to make HTTP POST requests # e.g., response = requests.post(url, params=kwargs, json=payload) @@ -18,12 +36,57 @@ # - Call get_request() with specified arguments # - Parse JSON results into a CarDealer object list +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["rows"] + # 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) + return results + # 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, dealer_id, api_key): + results = [] + + # Call get_request with a URL parameter + json_result = get_request(url, dealerId=dealer_id, api_key=api_key) + + if json_result: + # Get the list of reviews + reviews = json_result + + for review in reviews: + review_obj = DealerReview( + dealership=review["dealership"], + name=review["name"], + purchase=review["purchase"], + review=review["review"], + purchase_date=review["purchase_date"], + car_make=review["car_make"], + car_model=review["car_model"], + car_year=review["car_year"], + sentiment=analyze_review_sentiments(review["review"]), # Analyze sentiment + id=review["id"] + ) + results.append(review_obj) + + return results # Create an `analyze_review_sentiments` method to call Watson NLU and analyze text # def analyze_review_sentiments(text): @@ -31,4 +94,27 @@ # - Get the returned sentiment label such as Positive or Negative +def analyze_review_sentiments(text): + url = "https://api.us-south.natural-language-understanding.watson.cloud.ibm.com/instances/b47940d0-c41e-43b6-8430-b92ee938ce46/v1/analyze?version=2020-08-01" + + params = { + "text": text, + "version": "2020-08-01", + "features": "sentiment", + "return_analyzed_text": True + } + + response = requests.get(url, params=params, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth('apikey', WATSON_NLU_API_KEY)) + + if response.status_code == 200: + json_response = response.json() + if "sentiment" in json_response: + return json_response["sentiment"]["document"]["label"] + else: + return "Unknown" + else: + return "API request failed with status code: " + str(response.status_code) + + + diff --git a/server/djangoapp/templates/djangoapp/about.html b/server/djangoapp/templates/djangoapp/about.html new file mode 100644 index 0000000000..ceb7b5fe42 --- /dev/null +++ b/server/djangoapp/templates/djangoapp/about.html @@ -0,0 +1,12 @@ + + +
+ +Home to the best cars in North America. We sell domestic and imported cars at reasonable prices.
+ + + diff --git a/server/djangoapp/templates/djangoapp/add_review.html b/server/djangoapp/templates/djangoapp/add_review.html index 768ddf508c..d4ddbc215a 100644 --- a/server/djangoapp/templates/djangoapp/add_review.html +++ b/server/djangoapp/templates/djangoapp/add_review.html @@ -12,5 +12,37 @@ +