diff --git a/.github/workflows/push-cf.yml b/.github/workflows/push-cf.yml index 5c2e9e2fe7..b55d6b7293 100644 --- a/.github/workflows/push-cf.yml +++ b/.github/workflows/push-cf.yml @@ -29,7 +29,7 @@ jobs: ibmcloud config --check-version=false shell: bash - name: Install CF plugin - run: ibmcloud cf install -f + run: ibmcloud install -f shell: bash - name: Log into IBM Cloud run: | @@ -39,8 +39,8 @@ jobs: run: ibmcloud target --cf-api https://api.$IBM_CLOUD_REGION.cf.cloud.ibm.com -r $IBM_CLOUD_REGION -o $IBM_CLOUD_ORG -s $IBM_CLOUD_SPACE shell: bash - name: List all applications - run: ibmcloud cf apps + run: ibmcloud apps shell: bash - name: Deploy manifest file - run: ibmcloud cf push -f ./$MANIFEST_NAME + run: ibmcloud push -f ./$MANIFEST_NAME shell: bash diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index b1039e16b8..35805db8ce 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -11,3 +11,25 @@ # CarMakeAdmin class with CarModelInline # Register models here + +from django.contrib import admin +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..92d1699155 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -1,27 +1,52 @@ from django.db import models from django.utils.timezone import now - -# 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(max_length=30) + description = models.CharField(max_length=300) + def __str__(self): + return self.name # 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): + car_make = models.ForeignKey(CarMake, on_delete=models.CASCADE) + name = models.CharField(max_length=30) + type_c = models.CharField(max_length=10, choices=(('Sedan', 'Sedan',), ('SUV', 'SUV'), ('HATCHBACK', 'HATCHBACK'),('WAGON', 'WAGON'))) + dealer_id = models.IntegerField() + year = models.DateField() + def __str__(self): + return self.name -# Create a plain Python class `CarDealer` to hold dealer data +# 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, purchase_date, car_make, car_model, car_year, sentiment, id): + self.dealership = dealership + self.name = name + self.purchase = purchase + self.review = review + self.purchase_date = purchase_date + self.car_make = car_make + self.car_model = car_model + self.car_year = car_year + self.sentiment = sentiment + self.id = id diff --git a/server/djangoapp/restapis.py b/server/djangoapp/restapis.py index b4d13f596a..6466cc108d 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -1,23 +1,72 @@ import requests +import os import json -# import related models here +from .models import CarDealer, DealerReview from requests.auth import HTTPBasicAuth # 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, **kwargs): + print(kwargs) + print("GET from {} ".format(url)) + try: + # 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 # Create a `post_request` to make HTTP POST requests -# e.g., response = requests.post(url, params=kwargs, json=payload) +def post_request(url, json_payload, **kwargs): + print("Payload: ", json_payload, ". Params: ", kwargs) + print(f"POST {url}") + try: + response = requests.post(url, headers={'Content-Type': 'application/json'}, + json=json_payload, 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 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_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["entries"] + # For each dealer object + for dealer_doc in dealers: + # 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): @@ -25,10 +74,44 @@ # - Parse JSON results into a DealerView object list +def get_dealer_reviews_from_cf(url, dealerId): + results = [] + # Call get_request with a URL parameter + json_result = get_request(url) + if json_result: + # Get the row list in JSON as dealers + reviews = json_result["entries"] + # For each dealer object + for review_doc in reviews: + # Create a CarDealer object with values in `doc` object + review_obj = DealerReview( + dealership=review_doc["dealership"], + name=review_doc["name"], + purchase=review_doc["purchase"], + review=review_doc["review"], + purchase_date=review_doc["purchase_date"], + car_make=review_doc["car_make"], + car_model=review_doc["car_model"], + car_year=review_doc["car_year"], + sentiment=analyze_review_sentiments(review_doc["review"]), + id=review_doc["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): # - Call get_request() with specified arguments # - Get the returned sentiment label such as Positive or Negative - - - +def analyze_review_sentiments(text): + URL = 'https://api.us-east.natural-language-understanding.watson.cloud.ibm.com/instances/4c1dae76-d689-45e0-8340-f55e03dccfc0' + API_KEY = os.getenv('NLU_API_KEY') + params = json.dumps({"text": text, "features": {"sentiment": {}}}) + response = requests.post( + URL, data=params, headers={'Content-Type': 'application/json'}, auth=HTTPBasicAuth('apikey', API_KEY) + ) + try: + return response.json()['sentiment']['document']['label'] + except KeyError: + return 'neutral' diff --git a/server/djangoapp/templates/djangoapp/about.html b/server/djangoapp/templates/djangoapp/about.html new file mode 100644 index 0000000000..88571c4251 --- /dev/null +++ b/server/djangoapp/templates/djangoapp/about.html @@ -0,0 +1,33 @@ + + + + + + Dealership Review + + + + + + + + + + + + + +

+ Welcome to Best Cars dealership, 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..a497c85434 100644 --- a/server/djangoapp/templates/djangoapp/add_review.html +++ b/server/djangoapp/templates/djangoapp/add_review.html @@ -1,16 +1,53 @@ + {% load static %} - + - + + - - \ No newline at end of file +
+
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + + + diff --git a/server/djangoapp/templates/djangoapp/contact.html b/server/djangoapp/templates/djangoapp/contact.html new file mode 100644 index 0000000000..0c88deb47c --- /dev/null +++ b/server/djangoapp/templates/djangoapp/contact.html @@ -0,0 +1,41 @@ + + + + + + Dealership Review + + + + + + + + + + + + +
+

+ Contacts information +

+
+
    Phone No: 555 555 5555
+ + +
+
+ + + diff --git a/server/djangoapp/templates/djangoapp/index.html b/server/djangoapp/templates/djangoapp/index.html index 1a9ee6e39a..a78e8fd2da 100644 --- a/server/djangoapp/templates/djangoapp/index.html +++ b/server/djangoapp/templates/djangoapp/index.html @@ -14,12 +14,39 @@ - - This is the index page of your Django app! + This is the index page of your Django app! + - - + diff --git a/server/djangoapp/templates/djangoapp/registration.html b/server/djangoapp/templates/djangoapp/registration.html index ae11ea4b71..72256b52f8 100644 --- a/server/djangoapp/templates/djangoapp/registration.html +++ b/server/djangoapp/templates/djangoapp/registration.html @@ -7,5 +7,35 @@ + + +
+
+

Sign Up

+
+
+ + + + + + + + +
+
+ {% csrf_token %} + +
+
+
- \ No newline at end of file + diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 37b1c89d01..4ece729741 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -8,21 +8,27 @@ # route is a string contains a URL pattern # view refers to the view function # name the URL + path(route='', view=views.get_dealerships, name='index'), + # 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(route='', view=views.get_dealerships, name='index'), - + # path for dealer reviews view # path for add a review view + -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/server/djangoapp/views.py b/server/djangoapp/views.py index 61cc664da0..361b596c64 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -3,7 +3,9 @@ from django.contrib.auth.models import User from django.shortcuts import get_object_or_404, render, redirect # from .models import related models +from .models import CarModel,CarMake,CarDealer,DealerReview # from .restapis import related methods +from .restapis import get_dealers_from_cf, get_dealer_reviews_from_cf, post_request from django.contrib.auth import login, logout, authenticate from django.contrib import messages from datetime import datetime @@ -13,42 +15,123 @@ # Get an instance of a logger logger = logging.getLogger(__name__) - # Create your views here. - - # 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) # Create a `login_request` view to handle sign in request -# def login_request(request): -# ... +def login_request(request): + context = {} + if request.method == "POST": + # pull from dictionary + username = request.POST['username'] + password = request.POST['psw'] + # check auth + user = authenticate(username=username, password=password) + if user is not None: + # login if valid + login(request, user) + return render(request, 'djangoapp/index.html', context) + else: + return render(request, 'djangoapp/index.html', context) + else: + return render(request, 'djangoapp/index.html', context) # Create a `logout_request` view to handle sign out request -# def logout_request(request): -# ... +def logout_request(request): + context = {} + # get user from session id + print("Log out the user `{}`".format(request.user.username)) + logout(request) + # redirect back to the index.html + return render(request, 'djangoapp/index.html', context) # Create a `registration_request` view to handle sign up request -# def registration_request(request): -# ... +def registration_request(request): + context = {} + # rend if it is a GET req + if request.method == 'GET': + return render(request, 'djangoapp/registration.html', context) + elif request.method == 'POST': + # get user info + 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.debug("{} is new user".format(username)) + if not user_exist: + # create new user + user = User.objects.create_user(username=username, first_name=first_name, last_name=last_name, password=password) + login(request, user) + return render(request, 'djangoapp/index.html', context) + else: + return render(request, 'djangoapp/index.html', context) -# 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://service.eu.apiconnect.ibmcloud.com/gws/apigateway/api/a9220b6d6b26f1eb3b657a98770b743616f7d4cd223b89cd1ca4e88ab49bdb92/api/dealership" + # Get dealers from the URL + context = { + "dealerships": get_dealers_from_cf(url), + } 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, dealer_id): + if request.method == "GET": + url_r = f"https://service.eu.apiconnect.ibmcloud.com/gws/apigateway/api/a9220b6d6b26f1eb3b657a98770b743616f7d4cd223b89cd1ca4e88ab49bdb92/api/review?dealerId={dealer_id}" + url_ds = f"https://service.eu.apiconnect.ibmcloud.com/gws/apigateway/api/a9220b6d6b26f1eb3b657a98770b743616f7d4cd223b89cd1ca4e88ab49bdb92/api/dealership?dealerId={dealer_id}" + # Get dealers from the URL + context = { + "dealer": get_dealers_from_cf(url_ds)[0], + "reviews": get_dealer_reviews_from_cf(url_r, dealer_id), + } + 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, dealer_id): + if request.method == "GET": + url = f"https://service.eu.apiconnect.ibmcloud.com/gws/apigateway/api/a9220b6d6b26f1eb3b657a98770b743616f7d4cd223b89cd1ca4e88ab49bdb92/api/dealership?dealerId={dealer_id}" + + context = { + "cars": CarModel.objects.all(), + "dealer": get_dealers_from_cf(url)[0], + } + print(context) + return render(request, 'djangoapp/add_review.html', context) + if request.method == "POST": + form = request.POST + review = { + "name": f"{request.user.first_name} {request.user.last_name}", + "dealership": dealer_id, + "review": form["content"], + "purchase": form.get("purchasecheck"), + } + if form.get("purchasecheck"): + review["purchasedate"] = datetime.strptime(form.get("purchasedate"), "%m/%d/%Y").isoformat() + car = CarModel.objects.get(pk=form["car"]) + review["car_make"] = car.car_make.name + review["car_model"] = car.name + review["car_year"]= car.year.strftime("%Y") + json_payload = {"review": review} + URL = 'https://service.eu.apiconnect.ibmcloud.com/gws/apigateway/api/a9220b6d6b26f1eb3b657a98770b743616f7d4cd223b89cd1ca4e88ab49bdb92/api/review' + post_request(URL, json_payload, dealerId=dealer_id) + return redirect("djangoapp:dealer_details", dealer_id=dealer_id) +