diff --git a/server/deployment.yaml b/server/deployment.yaml new file mode 100644 index 0000000000..8ee3263ce3 --- /dev/null +++ b/server/deployment.yaml @@ -0,0 +1,30 @@ +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/your_namespace/dealership:latest + imagePullPolicy: Always + name: dealership + ports: + - containerPort: 8000 + protocol: TCP + restartPolicy: Always + diff --git a/server/djangoapp/admin.py b/server/djangoapp/admin.py index b1039e16b8..e2ff92fd9f 100644 --- a/server/djangoapp/admin.py +++ b/server/djangoapp/admin.py @@ -1,13 +1,16 @@ +# djangoapp/admin.py from django.contrib import admin -# from .models import related models +from .models import CarMake, CarModel +class CarModelInline(admin.TabularInline): + model = CarModel + extra = 1 # Number of empty forms to display for adding related CarModel objects -# Register your models here. +@admin.register(CarMake) +class CarMakeAdmin(admin.ModelAdmin): + inlines = [CarModelInline] -# CarModelInline class +@admin.register(CarModel) +class CarModelAdmin(admin.ModelAdmin): + list_display = ('car_make', 'name', 'car_type', 'year', 'dealer_id') # Customize the fields displayed in the admin list view -# CarModelAdmin class - -# CarMakeAdmin class with CarModelInline - -# Register models here diff --git a/server/djangoapp/models.py b/server/djangoapp/models.py index 27d96f4eff..bd30255597 100644 --- a/server/djangoapp/models.py +++ b/server/djangoapp/models.py @@ -9,6 +9,19 @@ # - 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 of the car make + name = models.CharField(max_length=100) + + # Description of the car make + description = models.TextField() + + # Any other fields you'd like to include + # For example, founding year of the car make + founding_year = models.IntegerField() + + def __str__(self): + return self.name # Create a Car Model model `class CarModel(models.Model):`: @@ -19,9 +32,76 @@ # - 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): + # Many-to-One relationship to CarMake model + car_make = models.ForeignKey(CarMake, on_delete=models.CASCADE) + + # Dealer ID referring to a dealer in Cloudant database + dealer_id = models.IntegerField() + + # Name of the car model + name = models.CharField(max_length=100) + + # Type of the car model with limited choices + CAR_TYPES = [ + ('SEDAN', 'Sedan'), + ('SUV', 'SUV'), + ('WAGON', 'Wagon'), + ] + car_type = models.CharField(max_length=5, choices=CAR_TYPES) + + # Year of the car model + year = models.DateField() + + # Any other fields you'd like to include + # For example, engine type, fuel type, etc. + engine_type = models.CharField(max_length=50) + + def __str__(self): + return f"{self.car_make} - {self.name} ({self.year.year})" # Create a plain Python class `CarDealer` to hold dealer data +class CarDealer(models.Model): + # Fields for CarDealer model + name = models.CharField(max_length=100) + location = models.CharField(max_length=255) + # Add any other fields you want for the CarDealer model + full_name = models.CharField(max_length=100) + id = models.IntegerField(primary_key=True) # Assuming id is the primary key + lat = models.FloatField() + long = models.FloatField() + short_name = models.CharField(max_length=50) + st = models.CharField(max_length=50) + zip = models.CharField(max_length=20) + def __str__(self): + return "Dealer name: " + self.full_name # Create a plain Python class `DealerReview` to hold review data +class DealerReview(models.Model): + # Many-to-One relationship to CarDealer model + car_dealer = models.ForeignKey(CarDealer, on_delete=models.CASCADE) + + # Fields for DealerReview model + reviewer_name = models.CharField(max_length=100) + review_text = models.TextField() + rating = models.PositiveIntegerField() + dealership = models.CharField(max_length=100) + name = models.CharField(max_length=100) + purchase = models.CharField(max_length=100) + 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 = models.CharField(max_length=10) # Positive, Neutral, Negative + id = models.IntegerField(primary_key=True) # Assuming this is the unique identifier + + def __str__(self): + return f"{self.dealership} - {self.name}" + + def __str__(self): + return f"{self.car_dealer} - {self.reviewer_name}" + + diff --git a/server/djangoapp/restapis.py b/server/djangoapp/restapis.py index b4d13f596a..cf73c8f495 100644 --- a/server/djangoapp/restapis.py +++ b/server/djangoapp/restapis.py @@ -2,33 +2,145 @@ import json # import related models here from requests.auth import HTTPBasicAuth - +from .models import CarDealer, DealerReview # 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 an API key is provided, use authentication + if api_key: + response = requests.get( + url, + headers={'Content-Type': 'application/json'}, + params=kwargs, + auth=HTTPBasicAuth('apikey', api_key) + ) + else: + # If no API key is provided, make a regular GET request + 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(kwargs) + print("POST to {} ".format(url)) + try: + # Call post method of requests library with URL, JSON payload, and parameters + response = requests.post(url, json=json_payload, 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 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, **kwargs) + 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 +# restapis.py + +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) + if json_result: + # Get the row list in JSON as reviews + reviews = json_result["reviews"] + # For each review object + for review in reviews: + # Create a DealerReview object with values in the review object + 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=None # Initialize sentiment as None + ) + + # Analyze sentiment and assign to the review object + review_obj.sentiment = analyze_review_sentiments(api_key, review_obj.review) + + 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(api_key, text): + # Watson NLU URL for analyzing sentiment + url = "your_watson_nlu_url/v1/analyze" + + # Parameters for the Watson NLU request + params = { + "text": text, + "version": "2021-03-25", # Specify the version of Watson NLU + "features": "emotion,sentiment", + "return_analyzed_text": True + } + + try: + # Call get_request with Watson NLU URL and parameters + response = get_request(url, api_key=api_key, **params) + + # Check if the response contains sentiment information + if "sentiment" in response: + return response["sentiment"] + else: + return None + except Exception as e: + print(f"Error analyzing sentiment: {e}") + return None + + + diff --git a/server/djangoapp/templates/djangoapp/About.html b/server/djangoapp/templates/djangoapp/About.html new file mode 100644 index 0000000000..637cf1f7a5 --- /dev/null +++ b/server/djangoapp/templates/djangoapp/About.html @@ -0,0 +1,20 @@ + + + + + + About Us + + +

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/ContactUs.html b/server/djangoapp/templates/djangoapp/ContactUs.html new file mode 100644 index 0000000000..5561a4da3b --- /dev/null +++ b/server/djangoapp/templates/djangoapp/ContactUs.html @@ -0,0 +1,18 @@ + + + + + Contact Us - Best Cars Dealership + + +

Contact Us

+

+ Welcome to Best Cars Dealership. For inquiries, please contact us at the following address and telephone number: +

+

+ Address: 123 Main Street, Cityville
+ Telephone: +1 (123) 456-7890 +

+ Contact Us + + diff --git a/server/djangoapp/templates/djangoapp/add_review.html b/server/djangoapp/templates/djangoapp/add_review.html index 768ddf508c..434702a5ba 100644 --- a/server/djangoapp/templates/djangoapp/add_review.html +++ b/server/djangoapp/templates/djangoapp/add_review.html @@ -8,9 +8,55 @@ - - - - - \ No newline at end of file + + + +
+

Add Review

+
+ {% csrf_token %} + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + + + +
+
+ + + diff --git a/server/djangoapp/templates/djangoapp/dealer_details.html b/server/djangoapp/templates/djangoapp/dealer_details.html index 25bd9a223d..0d91be8800 100644 --- a/server/djangoapp/templates/djangoapp/dealer_details.html +++ b/server/djangoapp/templates/djangoapp/dealer_details.html @@ -7,12 +7,44 @@ - + - + + - + +
+ {% for review in reviews %} +
+ {% if review.sentiment == 'positive' %} + Positive Sentiment + {% elif review.sentiment == 'neutral' %} + Neutral Sentiment + {% else %} + Negative Sentiment + {% endif %} +
+
{{ review.car_make }} {{ review.car_model }} - {{ review.car_year }}
+
{{ review.reviewer_name }}
+

{{ review.review_text }}

+
+
+ {% endfor %} +
- + - \ No newline at end of file + diff --git a/server/djangoapp/templates/djangoapp/index.html b/server/djangoapp/templates/djangoapp/index.html index 1a9ee6e39a..9d15650aa3 100644 --- a/server/djangoapp/templates/djangoapp/index.html +++ b/server/djangoapp/templates/djangoapp/index.html @@ -10,16 +10,79 @@ - - - - This is the index page of your Django app! - + + + - + +

Dealerships

+ + + + + + + + + + + + + {% for dealer in dealership_list %} + + + + + + + + + {% endfor %} + +
IDDealer NameCityAddressZipState
{{ dealer.id }}{{ dealer.full_name }}{{ dealer.city }}{{ dealer.address }}{{ dealer.zip }}{{ dealer.st }}
- + + + + + + diff --git a/server/djangoapp/templates/djangoapp/registration.html b/server/djangoapp/templates/djangoapp/registration.html index ae11ea4b71..2fb29e3788 100644 --- a/server/djangoapp/templates/djangoapp/registration.html +++ b/server/djangoapp/templates/djangoapp/registration.html @@ -7,5 +7,30 @@ +
+

Sign Up

+
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+

Already have an account? Login

+
- \ No newline at end of file + diff --git a/server/djangoapp/urls.py b/server/djangoapp/urls.py index 37b1c89d01..b4327152f0 100644 --- a/server/djangoapp/urls.py +++ b/server/djangoapp/urls.py @@ -2,27 +2,49 @@ from django.conf.urls.static import static from django.conf import settings from . import views +from .views import static_template_view, about_us_view, contact_us_view, login_view, logout_view, signup_view +from .views import get_dealerships app_name = 'djangoapp' urlpatterns = [ # route is a string contains a URL pattern # view refers to the view function # name the URL + path('static-template/', static_template_view, name='static_template'), + # Add more URL patterns if needed + # path for about view + path('about/', about_us_view, name='about_us'), # path for contact us view + path('contact/', contact_us_view, name='contact_us'), # path for registration + path('signup/', signup_view, name='signup'), # path for login + path('login/', login_view, name='login'), # path for logout + path('logout/', logout_view, name='logout'), path(route='', view=views.get_dealerships, name='index'), + + + path('get_dealerships/', get_dealerships, name='get_dealerships'), + + path('dealer//', views.get_dealer_details, name='dealer_details'), + # path for dealer reviews view + # path for add a review view + path('dealer//add_review/', views.add_review, name='add_review') + + +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + + -] + 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..0483fac5ea 100644 --- a/server/djangoapp/views.py +++ b/server/djangoapp/views.py @@ -1,4 +1,4 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect from django.http import HttpResponseRedirect, HttpResponse from django.contrib.auth.models import User from django.shortcuts import get_object_or_404, render, redirect @@ -7,34 +7,68 @@ from django.contrib.auth import login, logout, authenticate from django.contrib import messages from datetime import datetime +from django.contrib.auth.forms import UserCreationForm import logging import json - +from django.http import JsonResponse +from .models import CarDealer +from .restapis import get_dealers_from_cf +from .restapis import get_dealer_reviews_from_cf # Get an instance of a logger logger = logging.getLogger(__name__) # Create your views here. - +def static_template_view(request): + return render(request, 'djangoapp/static_template.html') # Create an `about` view to render a static about page +def about_us_view(request): + return render(request, 'djangoapp/about.html') # def about(request): # ... # Create a `contact` view to return a static contact page -#def contact(request): +def contact_us_view(request): + return render(request, 'djangoapp/contact.html') # Create a `login_request` view to handle sign in request -# def login_request(request): +def login_view(request): + if request.method == 'POST': + username = request.POST.get('username') + password = request.POST.get('password') + user = authenticate(request, username=username, password=password) + + if user is not None: + login(request, user) + messages.success(request, 'Successfully logged in.') + return redirect('index') # Replace 'index' with the name of your home page URL + else: + messages.error(request, 'Invalid username or password.') + + return render(request, 'djangoapp/index.html') # ... # Create a `logout_request` view to handle sign out request -# def logout_request(request): +def logout_view(request): + logout(request) + messages.success(request, 'Successfully logged out.') + return redirect('index') # ... # Create a `registration_request` view to handle sign up request -# def registration_request(request): +def signup_view(request): + if request.method == 'POST': + form = UserCreationForm(request.POST) + if form.is_valid(): + user = form.save() + login(request, user) + return redirect('index') # Redirect to the desired page after signup + else: + form = UserCreationForm() + + return render(request, 'djangoapp/registration.html', {'form': form}) # ... # Update the `get_dealerships` view to render the index page with a list of dealerships @@ -47,8 +81,65 @@ def get_dealerships(request): # 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 = "your-cloud-function-domain/reviews/review-get" + api_key = "your_watson_nlu_api_key" # Replace with your actual Watson NLU API key + + # Get reviews from the URL + dealer_reviews = get_dealer_reviews_from_cf(url, dealer_id, api_key) + + # Print details for each review, including sentiment + for review in dealer_reviews: + print(f"Review: {review.review}") + print(f"Sentiment: {review.sentiment}") + print("-" * 30) + + # Return a response (you can customize this part based on your needs) + return HttpResponse("Reviews and Sentiments Printed in Console") # Create a `add_review` view to submit a review # def add_review(request, dealer_id): # ... +def add_review(request, dealer_id): + # Check if the request method is POST + if request.method == "POST": + url = "your-cloud-function-domain/reviews/review-post" + api_key = "your_watson_nlu_api_key" # Replace with your actual Watson NLU API key + + # Create a dictionary object for the review + review = { + "time": datetime.utcnow().isoformat(), + "name": request.user.username, # Assuming the username is used as the reviewer's name + "dealership": dealer_id, + "review": request.POST.get("review", ""), # You can customize this based on your form fields + "purchase": bool(request.POST.get("purchase", False)), # Example: a checkbox indicating purchase + # Add other attributes based on your review-post cloud function + } + + # Create a JSON payload with the review + json_payload = {"review": review} + + # Make the POST request to add a review + response = post_request(url, json_payload, dealerId=dealer_id, api_key=api_key) + + # Return the response (you can customize this part based on your needs) + return HttpResponse(f"Review added successfully! Response: {response}") + else: + # Handle other HTTP methods (GET, etc.) as needed + return HttpResponse("Invalid HTTP method") + + + + +# get_dealerships +def get_dealerships(request): + if request.method == "GET": + url = "https://us-south.functions.appdomain.cloud/api/v1/web/7aa8825a-d560-42e5-9f7c-8fbeea4a7ebb/dealership-package/get-dealership" + # Get dealers from the URL + dealerships = get_dealers_from_cf(url) + # Concat all dealer's short name + dealer_names = ' '.join([dealer.short_name for dealer in dealerships]) + # Return a list of dealer short name + return HttpResponse(dealer_names) diff --git a/server/djangobackend/settings.py b/server/djangobackend/settings.py index ff43f444a8..cf799b8053 100644 --- a/server/djangobackend/settings.py +++ b/server/djangobackend/settings.py @@ -1,36 +1,15 @@ -""" -Django settings for djangobackend project. - -Generated by 'django-admin startproject' using Django 3.1.3. - -For more information on this file, see -https://docs.djangoproject.com/en/3.1/topics/settings/ - -For the full list of settings and their values, see -https://docs.djangoproject.com/en/3.1/ref/settings/ -""" import os from pathlib import Path -# Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'ao5z(o(z@cvzodm99d32jkxa5e8a1!q_4sqss5-a%n6tg$#h$+' -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = False APPEND_SLASH = True -ALLOWED_HOSTS = ["localhost"] - - -# Application definition +ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ 'djangoapp.apps.DjangoappConfig', @@ -73,10 +52,6 @@ WSGI_APPLICATION = 'djangobackend.wsgi.application' - -# Database -# https://docs.djangoproject.com/en/3.1/ref/settings/#databases - DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -84,10 +59,6 @@ } } - -# Password validation -# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators - AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -103,25 +74,24 @@ }, ] - -# Internationalization -# https://docs.djangoproject.com/en/3.1/topics/i18n/ - LANGUAGE_CODE = 'en-us' - TIME_ZONE = 'UTC' - USE_I18N = True - USE_L10N = True - USE_TZ = True - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.1/howto/static-files/ - STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') MEDIA_ROOT = os.path.join(STATIC_ROOT, 'media') MEDIA_URL = '/media/' + +# Additional settings to address CSRF verification failure +CSRF_TRUSTED_ORIGINS = [ + "https://chawlasaurab-8000.theiadockernext-0-labs-prod-theiak8s-4-tor01.proxy.cognitiveclass.ai", +] + +CSRF_COOKIE_SECURE = False +CSRF_COOKIE_SAMESITE = None +LOGGING = { + # your logging configuration +} diff --git a/server/docker b/server/docker new file mode 100644 index 0000000000..cc704791c6 --- /dev/null +++ b/server/docker @@ -0,0 +1,24 @@ +FROM python:3.12.0-slim-bookworm + +ENV PYTHONBUFFERED 1 +ENV PYTHONWRITEBYTECODE 1 +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 + +# Set up entrypoint script +COPY entrypoint.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh + +ENTRYPOINT ["/bin/bash", "/app/entrypoint.sh"] +CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "djangobackend.wsgi"] diff --git a/server/entrypoint.sh b/server/entrypoint.sh new file mode 100644 index 0000000000..e7f56a09a2 --- /dev/null +++ b/server/entrypoint.sh @@ -0,0 +1,15 @@ +#!/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 --noinput +python manage.py migrate --noinput + +exec "$@"