diff --git a/WBBackend/database/database.csv b/WBBackend/database/database.csv new file mode 100644 index 0000000..70bfdca --- /dev/null +++ b/WBBackend/database/database.csv @@ -0,0 +1,7 @@ +Name, Age, starting_location, Carbon_Offset +Cheddar,23,Castle Park,483 +McCheese,18,California Citrus State Historic Park,801 +GoodBoy,34,UC Riverside Botanic Gardens,11 +Jackson,48,Andulka Park,420 +GreenBean,39,Riverside Art Museum,69 +Mochi,21,Doty Trust Park,41 diff --git a/WBBackend/database/database_functions.py b/WBBackend/database/database_functions.py new file mode 100644 index 0000000..950bcd7 --- /dev/null +++ b/WBBackend/database/database_functions.py @@ -0,0 +1,35 @@ +import pandas as pd +import ./components/form.js + +tripdetails = ["Name", "Age", "starting_location", "Carbon_Offset", ] + +# write new trip data into table +def write_new(): + df = pd.read_csv('csvfilepath') + + # how to store info fromtheform into somearray + + # second dataframe for new row + df_new = pd.DataFrame(data=somearray, columns=tripdetails) + + # add new row to original data + df.append(df_new) + + # update csv file + trips_df.to_csv + + return + +# read row at index +def read(index): + df = pd.read_csv('csvfilepath') + + # store in multiple variables? + # photofile = df.loc[index, 'Photofile'] + # ... + + # store in one dataframe + df_read = pd.DataFrame(data=df.loc([index], columns=tripdetails) + +def write(index): + return df.to_csv("test.txt", columns=[index], header=False, index=False) diff --git a/WBBackend/database/form.js b/WBBackend/database/form.js new file mode 100644 index 0000000..d08de36 --- /dev/null +++ b/WBBackend/database/form.js @@ -0,0 +1,30 @@ +window.addEventListener( "load", function () { + function sendData(data) { + const XHR = new XMLHttpRequest(); + + const FD = new FormData(form); + + for(i in data) { + FD.append(i,data[name]); + } + + XHR.addEventListener( "load", function(event) { + alert( event.target.responseText ); + } ); + + XHR.addEventListener( "error", function( event ) { + alert( 'Oops! Something went wrong.' ); + } ); + + XHR.open( "POST", "./database_functions.py" ); + + XHR.send(FD); + } + let form = document.getElementById( "form" ); + + form.addEventListener( "submit", function ( event ) { + event.preventDefault(); + + sendData(); + } ); +} ); diff --git a/WBBackend/database/list.txt b/WBBackend/database/list.txt new file mode 100644 index 0000000..e69de29 diff --git a/WBBackend/twilio_functions.py b/WBBackend/twilio_functions.py new file mode 100644 index 0000000..964c45a --- /dev/null +++ b/WBBackend/twilio_functions.py @@ -0,0 +1,68 @@ +from twilio.rest import Client +from twilio.twiml.messaging_response import MessagingResponse +import os + +TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID') +TWILIO_AUTH_TOKEN = os.environ.get('TWILIO_AUTH_TOKEN') +TWILIO_PHONE_NUMBER = '+12166665403' +client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) #twilio account client + +import datetime as dt +CAVEMANAGER = ("Desolation Wilderness", "Ranger Maria", 7038877463) #to be hard coded into device +############################################## +############################################## +# UPDATE: to be imported somehow from database +############################################## +############################################## +check_in = {"photo": "123.jpg", "date_time": dt.datetime(2020,3,7,10,0), +"user": (123456, "Sonia Meyer", 7038877463), "group_size": 3, +"expected_out": dt.datetime(2020,3,7,18,0), "call_out": dt.datetime(2020,3,8,8,0)} +check_out = {"photo": "456.jpg", "date_time": dt.datetime(2020,3,7,18,30), +"user": (123456, "Sonia Meyer", 7038877463), "group_size": 3} +missed_checkout = {"photo": "456.jpg", "date_time": dt.datetime(2020,3,7,23,00), +"user": (123456, "Sonia Meyer", 7038877463), "group_size": 3} +############################################## +############################################## + +############################################## +############################################## +def check_user_status(user): + '''the check_user_status function references the database and returns + user status whether they are in or out of the cave''' + if user == "in": return False + if user == "out": return True +############################################## +############################################## + +def checked_out(): + '''the checked_out will send user a confirmation message''' + client.messages.create( + to='+1'+str(check_in["user"][2]), #user phone number + from_=TWILIO_PHONE_NUMBER, + body='Thanks for visiting {}, you are checked out!'.format(CAVEMANAGER[0]) + ) + return + +def missed_expected_out(): + '''the missed_expected_out will check user status at initiate_contact time, + then check with user if they forgot to check out of cave''' + if check_user_status("in"): + return + client.messages.create( + to='+1'+str(check_in["user"][2]), #user phone number + from_=TWILIO_PHONE_NUMBER, + body='Thanks for visiting {}, did you exit with all {} people?'.format(CAVEMANAGER[0],check_in["group_size"]) + ) + return + +def missed_call_out(): + '''the missed_call_out will check user status at call out time, then notify + the cave manager to initiate rescue if user is not out''' + if check_user_status("in"): + return + client.messages.create( + to='+1'+str(CAVEMANAGER[2]), #manager phone number + from_=TWILIO_PHONE_NUMBER, + body='{}, {} was visiting {} and did not check out before their call out time of {}.'.format(CAVEMANAGER[1],check_in["user"][1],CAVEMANAGER[0],check_in["call_out"]) + ) + return diff --git a/WBCarPoolApp/RecommendationAlgorithm.py b/WBCarPoolApp/RecommendationAlgorithm.py new file mode 100644 index 0000000..08a9d33 --- /dev/null +++ b/WBCarPoolApp/RecommendationAlgorithm.py @@ -0,0 +1,120 @@ +import googlemaps +from datetime import datetime + + + +ApiKey = "" +# Place Key here + +gmaps = googlemaps.Client(key=ApiKey) +# Passes api key to each query + +DriverStart = input("Where are you starting from: ") +PassengerLocations = ["Castle Park", "California Citrus State Historic Park","UC Riverside Botanic Gardens","Andulka Park",] +Destination = input("Where are you going: ") +# Collecting data from user as well as defining locations of potenial riders + +ExtraMinPerLocation = { + "Castle Park": [] , + "California Citrus State Historic Park": [], + "UC Riverside Botanic Gardens": [], + "Andulka Park":[], + "Riverside Art Museum": [], + } +ExtraMin = [] +# Sets up a dic and list wich will be used for operations later +now = datetime.now() +# Gets current time + +for locations in PassengerLocations: + # For every location in the list of passenger locations the following caluclations are completed + OrignalTime = gmaps.directions( DriverStart, + Destination, + optimize_waypoints = True, + mode = "driving", + traffic_model = "best_guess", + departure_time = now) +# Calls first query asking for duration of the drivers orginal dirve +======= +ApiKey="" + # Place Key here + +gmaps = googlemaps.Client(key=ApiKey) +DriverStart = input("Where are you starting from: ") +PassengerLocations = ["Fresno", "UCSB","CSUSB","UCSC","Museum of Modern Art"] +Destination = input("Where are you going: ") +ExtraMinPerLocation = { + "Fresno": [] , + "UCSB": [], + "CSUSB": [], + "UCSC":[], + "Museum of Modern Art": [], + + } +ExtraMin = [] +now = datetime.now() + +for locations in PassengerLocations: + OrignalTime = gmaps.directions( DriverStart, + Destination, + optimize_waypoints = True, + mode = "driving", + traffic_model = "best_guess", + departure_time = now) + + Firstleg = gmaps.directions( DriverStart, + locations, + optimize_waypoints = True, + mode = "driving", + traffic_model = "best_guess", + departure_time = now) + + Secondleg = gmaps.directions( locations, + Destination, + optimize_waypoints = True, + mode = "driving", + traffic_model = "best_guess", + departure_time = now) + +# Second two queries are from drivers start to riders start and riders start to the destination +======= + + OrignalTime = OrignalTime[0]['legs'][0]['duration']['value'] + FirstLegDuration = Firstleg[0]['legs'][0]['duration']['value'] + SecondLegDuration = Secondleg[0]['legs'][0]['duration']['value'] + +# Parses Data from API +======= + + TotalDuration = FirstLegDuration + SecondLegDuration + ExtraTime = TotalDuration - OrignalTime + + ExtraMin.append(ExtraTime) + ExtraMinPerLocation[locations].append(ExtraTime) + + # Finds how much added time the ride will take and adds it to the list and dic from before +======= + + + +sorted(ExtraMinPerLocation) +ExtraMin.sort() + +# Sorts so the shortest time will come first + +BestOption = str(next(iter(ExtraMinPerLocation))) +# Grabs the best option from the front of the list + +minutes = round(ExtraMin[0] / 60,2) +# Grabs the added time in seconds thne turns it into min and rounds + +print(f"There is someone you can pick up at {BestOption}. And it will only add {minutes} minutes to your route!") +# Prints results +======= + +BestOption = str(next(iter(ExtraMinPerLocation))) + +minutes = round(ExtraMin[0] / 60,2) + +print(f"There is someone you can pick up at {BestOption}. And it will only add {minutes} minutes to your route!") + diff --git a/WBCarPoolApp/chat/__init__.py b/WBCarPoolApp/chat/__init__.py new file mode 100644 index 0000000..070e835 --- /dev/null +++ b/WBCarPoolApp/chat/__init__.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import, unicode_literals + +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery import app as celery_app + +__all__ = ('celery_app',) diff --git a/WBCarPoolApp/chat/celery.py b/WBCarPoolApp/chat/celery.py new file mode 100644 index 0000000..16c488d --- /dev/null +++ b/WBCarPoolApp/chat/celery.py @@ -0,0 +1,22 @@ +from __future__ import absolute_import, unicode_literals +import os +from celery import Celery +from django.conf import settings +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat.settings') + +app = Celery('chat') + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks(settings.INSTALLED_APPS) + + +@app.task(bind=True) +def debug_task(self): + print('Request: {0!r}'.format(self.request)) diff --git a/WBCarPoolApp/chat/models.py b/WBCarPoolApp/chat/models.py new file mode 100644 index 0000000..f4f7606 --- /dev/null +++ b/WBCarPoolApp/chat/models.py @@ -0,0 +1,13 @@ +# chat/models.py + +from django.db import models + +class Room(models.Model): + """Represents chat rooms that users can join""" + name = models.CharField(max_length=30) + description = models.CharField(max_length=100) + slug = models.CharField(max_length=50) + + def __str__(self): + """Returns human-readable representation of the model instance.""" + return self.name diff --git a/WBCarPoolApp/chat/settings.py b/WBCarPoolApp/chat/settings.py new file mode 100644 index 0000000..6b1b55d --- /dev/null +++ b/WBCarPoolApp/chat/settings.py @@ -0,0 +1,183 @@ +""" +Django settings for WBCarPoolApp project. + +Generated by 'django-admin startproject' using Django 2.2.7. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.2/ref/settings/ +""" +import os + +import dj_database_url +from decouple import config, Csv + +# Add this import at the top of the file +from dotenv import load_dotenv + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Add this anywhere below the BASE_DIR setting +dotenv_path = os.path.join(BASE_DIR, '.env') +load_dotenv(dotenv_path) + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = config('SECRET_KEY') +# SECURITY WARNING: don't run with debug turned on in production! +ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv()) + +DEBUG = config('DEBUG') +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sites', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'chat.apps.ChatConfig', + 'rest_framework', + 'rest_framework.authtoken', + 'rest_auth', + 'rest_auth.registration', + 'allauth', + 'allauth.account', + 'allauth.socialaccount', + 'allauth.socialaccount.providers.google', + 'WBBackend.apps.WbbackendConfig', + 'django_filters', + 'coreapi', + 'fcm_django', + 'drf_yasg', + 'corsheaders', +] +FCM_DJANGO_SETTINGS = { + "FCM_SERVER_KEY": config('FCM_SERVER_KEY') +} + +SITE_ID = 1 + +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' + +CORS_ORIGIN_ALLOW_ALL = True + +REST_FRAMEWORK = { + 'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'] , + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'rest_framework.authentication.TokenAuthentication', + ), + 'DEFAULT_PERMISSION_CLASSES': ( + 'rest_framework.permissions.IsAuthenticated', + 'rest_framework.permissions.AllowAny', + ), +} + +AUTHENTICATION_BACKENDS = ( + # Needed to login by username in Django admin, regardless of `allauth` + 'django.contrib.auth.backends.ModelBackend', + + # `allauth` specific authentication methods, such as login by e-mail + 'allauth.account.auth_backends.AuthenticationBackend', +) +MIDDLEWARE = [ + 'whitenoise.middleware.WhiteNoiseMiddleware', + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.middleware.common.CommonMiddleware', +] + +CORS_ORIGIN_ALLOW_ALL = True + +ROOT_URLCONF = 'WBCarPoolApp.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.media', + ], + }, + }, +] + +WSGI_APPLICATION = 'WBCarPoolApp.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/2.2/ref/settings/#databases + + +DATABASES = { + 'default': dj_database_url.config(default=config('DATABASE_URI')) +} + +# Password validation +# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + +# Internationalization +# https://docs.djangoproject.com/en/2.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'Africa/Nairobi' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.2/howto/static-files/ + +STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') +STATIC_URL = '/static/' + +# chatroom_app/settings.py + +TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID', None) +TWILIO_API_KEY = os.environ.get('TWILIO_API_KEY', None) +TWILIO_API_SECRET = os.environ.get('TWILIO_API_SECRET', None) +TWILIO_CHAT_SERVICE_SID = os.environ.get('TWILIO_CHAT_SERVICE_SID', None) + +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "static"), +] +MEDIA_URL = '/media/' +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') diff --git a/WBCarPoolApp/chat/urls.py b/WBCarPoolApp/chat/urls.py new file mode 100644 index 0000000..cfabf15 --- /dev/null +++ b/WBCarPoolApp/chat/urls.py @@ -0,0 +1,22 @@ +"""WBCarPoolApp URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path,include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('v1/apis/', include('WBBackend.urls')) +] diff --git a/WBCarPoolApp/chat/wsgi.py b/WBCarPoolApp/chat/wsgi.py new file mode 100644 index 0000000..1568891 --- /dev/null +++ b/WBCarPoolApp/chat/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for WBCarPoolApp project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat.settings') + +application = get_wsgi_application() diff --git a/WBCarPoolApp/settings.py b/WBCarPoolApp/settings.py index d6594e0..6b1b55d 100644 --- a/WBCarPoolApp/settings.py +++ b/WBCarPoolApp/settings.py @@ -14,9 +14,16 @@ import dj_database_url from decouple import config, Csv +# Add this import at the top of the file +from dotenv import load_dotenv + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Add this anywhere below the BASE_DIR setting +dotenv_path = os.path.join(BASE_DIR, '.env') +load_dotenv(dotenv_path) + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ @@ -36,6 +43,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'chat.apps.ChatConfig', 'rest_framework', 'rest_framework.authtoken', 'rest_auth', @@ -160,6 +168,14 @@ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') STATIC_URL = '/static/' + +# chatroom_app/settings.py + +TWILIO_ACCOUNT_SID = os.environ.get('TWILIO_ACCOUNT_SID', None) +TWILIO_API_KEY = os.environ.get('TWILIO_API_KEY', None) +TWILIO_API_SECRET = os.environ.get('TWILIO_API_SECRET', None) +TWILIO_CHAT_SERVICE_SID = os.environ.get('TWILIO_CHAT_SERVICE_SID', None) + STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static"), ] diff --git a/chat/__init__.py b/chat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chat/admin.py b/chat/admin.py new file mode 100644 index 0000000..8c1dce3 --- /dev/null +++ b/chat/admin.py @@ -0,0 +1,49 @@ +from django.contrib import admin + +from .models import * + + +admin.site.register(RequestBoard) +admin.site.register(Location) +admin.site.register(Demand) +admin.site.register(Trip) +admin.site.register(TripChat) +admin.site.register(TripDetail) + +# Register your models here. + + +@admin.register(Offer) +class Offer(admin.ModelAdmin): + list_display = ('driver','origin','destination','seats_needed', + 'departure_time','created_at', 'is_full','is_ended') + list_filter = ('id', 'driver', 'origin', 'destination', 'departure_time' , 'is_full', 'is_ended') + ordering = ('id',) + read_only_fields = ('driver') + + + +@admin.register(Profile) +class Profile(admin.ModelAdmin): + exclude = ('user',) + + list_display = ('first_name', 'last_name', 'phone_number', 'profile_pic',) + + list_filter = ('id', 'first_name', 'last_name',) + + ordering = ("id",) + + read_only_fields = ("user",) + + +@admin.register(UserData) +class UserData(admin.ModelAdmin): + exclude = ('user',) + + list_display = ('first_name', 'last_name', 'username','phone_number', 'email',) + + list_filter = ('first_name', 'last_name', 'email',) + + ordering = ("first_name",) + + read_only_fields = ("user",) diff --git a/chat/apps.py b/chat/apps.py new file mode 100644 index 0000000..39b9af9 --- /dev/null +++ b/chat/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig + +class WbbackendConfig(AppConfig): + name = 'WBBackend' + + def ready(self): + from . import signals diff --git a/chat/models.py b/chat/models.py new file mode 100644 index 0000000..051e16b --- /dev/null +++ b/chat/models.py @@ -0,0 +1,176 @@ +from django.contrib.auth.models import User +from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver + + +class UserData(models.Model): + first_name = models.CharField(max_length=200, null=True) + last_name = models.CharField(max_length=200, null=True) + username = models.CharField(max_length=200) + phone_number = models.CharField(max_length=200) + email = models.CharField(max_length=200) + + def __str__(self): + return f'{self.username}' + + class Meta: + db_table = 'userdata' + verbose_name = 'userdata' + verbose_name_plural = 'userdata' + +class Profile(models.Model): + first_name = models.CharField(max_length=200, null=True) + last_name = models.CharField(max_length=200, null=True) + phone_number = models.CharField(max_length=100, null=True) + profile_pic = models.ImageField(upload_to='profile_pics/', blank=True, default='sample.jpg', null=True) + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') + + @receiver(post_save, sender=User) + def create_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance, first_name=instance.username) + + @receiver(post_save, sender=User) + def save_profile(sender, instance, **kwargs): + instance.profile.save() + + def __str__(self): + return f'{self.first_name}' + + class Meta: + db_table = 'profile' + verbose_name = 'profile' + verbose_name_plural = 'profile' + + +# TODO :Remove the distance field and add it to offer and demand models +class Location(models.Model): + name = models.CharField(max_length=200) + lat = models.FloatField() + lng = models.FloatField() + + def __str__(self): + return f'{self.name}' + + class Meta: + db_table = 'location' + +class Offer(models.Model): + driver = models.ForeignKey('WBBackend.Profile', related_name='driver_profile', on_delete=models.CASCADE) + origin = models.ForeignKey('WBBackend.Location', related_name='offer_origin', on_delete=models.CASCADE) + destination = models.ForeignKey('WBBackend.Location', related_name='offer_destination', on_delete=models.CASCADE) + seats_needed = models.IntegerField() + departure_time = models.TimeField(null=True, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + is_full = models.BooleanField(default=False) + is_ended = models.BooleanField(default=False) + + def __str__(self): + return f'Offer by {self.driver} to {self.destination}' + + class Meta: + db_table = 'offer' + verbose_name = 'offer' + verbose_name_plural = 'offers' + +class Demand(models.Model): + passenger = models.ForeignKey('WBBackend.Profile', related_name='trip_passenger', on_delete=models.CASCADE) + origin = models.ForeignKey('WBBackend.Location', related_name='demand_origin', on_delete=models.CASCADE) + destination = models.ForeignKey('WBBackend.Location', related_name='demand_destination', on_delete=models.CASCADE) + available_seats = models.IntegerField() + departure_time = models.DateTimeField() + created_at = models.DateTimeField(auto_now_add=True) + distance = models.CharField(max_length=255) + complete = models.BooleanField(default=False) + canceled = models.BooleanField(default=False) + + def __str__(self): + return f'Demand by {self.passenger} for {self.destination}' + + class Meta: + db_table = 'demand' + verbose_name = 'demand' + verbose_name_plural = 'demands' + +class RequestBoard(models.Model): + PENDING = 'PE' + ACCEPTED = 'AC' + DENIDE = 'DE' + REQUEST_STATUS = [ + (PENDING, "Pending"), + (ACCEPTED, "Accepted"), + (DENIDE, "Denied"), + ] + offer = models.ForeignKey(Offer, related_name='requests', on_delete=models.CASCADE) + status = models.CharField(choices=REQUEST_STATUS, max_length=2) + demand = models.ForeignKey(Demand, related_name='my_request', on_delete=models.CASCADE) + + class Meta: + db_table = "board_request" + + def __str__(self): + return f'{self.demand.passenger.user.username} request to {self.offer.driver.user.username}' + +class TripDetail(models.Model): + request = models.ForeignKey(RequestBoard, on_delete=models.CASCADE) + offer = models.ForeignKey(Offer, on_delete=models.CASCADE) + demand = models.ForeignKey(Demand, on_delete=models.CASCADE) + + class Meta: + db_table = 'trip_details' + ordering = ['offer'] + + def __str__(self): + return f"{self.offer.driver.user.username}'s trip to {self.offer.destination.name}" + +class Trip(models.Model): + offer = models.ForeignKey(Offer, on_delete=models.CASCADE) + start_time = models.TimeField(null=True) + stop_time = models.TimeField(null=True) + + class Meta: + db_table = "trip" + ordering = ['offer'] + + def __str__(self): + return f'trip to {self.offer.destination}' + +class TripChat(models.Model): + user = models.ForeignKey(Profile, on_delete=models.CASCADE) + message = models.TextField() + offer = models.ForeignKey(Offer, on_delete=models.CASCADE) + time = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"{self.user.user.username}'s message" + + class Meta: + db_table = 'tripchat' + verbose_name = 'tripchat' + verbose_name_plural = 'tripchat' + +class Survey: + user = models.ForeignKey(Profile, on_delete=models.CASCADE) + question = models.CharField(max_length=20) + response = models.CharField(max_length=20) + + def __str__(self): + return f'This survey was submitted by {user.username}.' + + class Meta: + db_table = 'survey' + verbose_name = 'passenger_survey' + verbose_name_plural = 'passenger_surveys' + +class Emmissions: + trip = models.ForeignKey(Trip, on_delete=models.CASCADE) + saved_emmissions = models.FloatField() + + def __str__(self): + return f'saved {saved_emmissions}"%" of carbon emmissions' + + class Meta: + db_table = 'emmission' + verbose_name = 'saved_carbon_emmission' + verbose_name_plural = 'saved_carbon_emmisions' diff --git a/chat/tests.py b/chat/tests.py new file mode 100644 index 0000000..5297994 --- /dev/null +++ b/chat/tests.py @@ -0,0 +1,170 @@ +from django.test import TestCase +from django.test import Client + +from WBBackend.models import ( + UserData, + Profile, + ) + +# Create your tests here. + +# TODO: Learn how to use fixtures for testing + +class TestUserData(TestCase): + def setUp(self): + new_user = UserData.objects.create( + first_name = 'test', + last_name = 'tester', + username = 'test', + phone_number = '0700000000', + email = 'test@email.com' + ) + + # self.assertTrue(isinstance(new_user, self.UserData)) + + def test_user_instance(self): + pass + + def tearDown(self): + pass + + +class TestProfile(TestCase): + def setUp(self): + pass + + def test_profile_instance(self): + pass + + def test_profile_instance(self): + pass + + def tearDown(self): + pass + +class TestLocation(TestCase): + def setUp(self): + pass + + def test_location_instance(self): + pass + + def tearDown(self): + pass + + +class TestOffer(TestCase): + def setUp(self): + pass + + def test_offer_instance(self): + pass + + def tearDown(self): + pass + + +class TestDemand(TestCase): + def setUp(self): + pass + + def test_demand_instance(self): + pass + + def tearDown(self): + pass + + +class TestRequestBoard(TestCase): + def setUp(self): + pass + + def test_request_board_instance(self): + pass + + def tearDown(self): + pass + + +class TestTripDetail(TestCase): + def setUp(self): + pass + + def test_trip_details_instance(self): + pass + + def tearDown(self): + pass + + +class TestTrip(TestCase): + def setUp(self): + pass + + def test_trip_instance(self): + pass + + def tearDown(self): + pass + + +class TestTripChat(TestCase): + def setUp(self): + pass + + def test_trip_chat_instance(self): + pass + + def tearDown(self): + pass + + +class TestSurver(TestCase): + def setUp(self): + pass + + def test_survey_instance(self): + pass + + def tearDown(self): + pass + +class TestEmmisions(TestCase): + def setUp(self): + pass + + def test_emmisions_instance(self): + pass + + def tearDown(self): + pass + + + + + +#Test Url endpoints + +class TestRegisterEndpoint(TestCase): + def setUp(self): + client = Client() + response = client.get('/user_auth/?apiKey=r9y7naiFAZ0E7EFsuV2GSeXhG8K8Yg3NquVvEsaI&email=waichigovick@gmail.com') + + def test_register_response(self): + pass + + def tearDown(self): + pass + + +class TestLoginEndpoint(TestCase): + def setUp(self): + client = Client() + response = client.post('/login/', {'username':'vick', 'password': 'password'}) + + def test_login_response(self): + # self.assertEquals(self.response.status_code, 200) + pass + + def tearDown(self): + pass diff --git a/chat/views.py b/chat/views.py new file mode 100644 index 0000000..b20040c --- /dev/null +++ b/chat/views.py @@ -0,0 +1,233 @@ +from django.http import JsonResponse +from rest_framework.decorators import api_view,authentication_classes +from rest_framework.viewsets import ModelViewSet +from .models import * +from . import notification +from .serializers import * +from rest_framework.exceptions import ValidationError,MethodNotAllowed, NotFound, AuthenticationFailed +from rest_framework.permissions import IsAuthenticated +from django.http import JsonResponse +from rest_framework.response import Response +from rest_framework.views import APIView +from django.http import JsonResponse +from rest_framework.response import Response +from rest_framework.permissions import AllowAny +from rest_framework.authtoken.models import Token +from django.contrib.auth.models import User +# Create your views here. +from .serializers import ProfileSerializer +from WBBackend.validate_user import ValidateUser +from WBBackend.create_user import create_new_user,generate_code +from rest_framework.authentication import SessionAuthentication, BasicAuthentication + +class UserDataView(APIView): + queryset = UserData.objects.all() + serializer_class = UserDataSerializer + permission_classes_by_action = {'list': [AllowAny],} + + def get(self, request, format=None): + email = self.request.query_params.get('email') + api_key = self.request.query_params.get('apiKey') + print(api_key) + auth_code = generate_code() + if email is None: + raise ValidationError(detail='The email must be provided.' , code='400') + if api_key is None: + raise ValidationError(detail='The api_key must be provided.', code='400') + if api_key is None and email is None: + raise ValidationError(detail='The api_key and email must be provided.', code='400') + else: + # Validate passed user emails + try: + valid_email = ValidateUser.validate_email(email) + print(f'***VALIDATED*** {valid_email} Successfully.') + try: + #Check if a user exists and get user data + my_user = ValidateUser.check_if_user_exists(api_key, valid_email) + print(my_user) + + if my_user is None: + raise + + + #Call create_new_user function + create_new_user( + my_user['id'], + my_user['name'].split()[0], + my_user['name'].split()[1], + my_user['username'], + auth_code, + my_user['email'], + my_user['phone_number'] + ) + except Exception as e: + print(f'message : {e}') + except Exception as e: + print(f'message: {e}') + users = UserData.objects.filter(email=email) + print(users) + + serialized_users = UserDataSerializer(users, many=True) + return Response(serialized_users.data) + + def list(self, request, *args, **kwargs): + return super(UserDataView, self).list(request, *args, **kwargs) + + def get_permissions(self): + try: + # return permission_classes depending on `action` + return [permission() for permission in self.permission_classes_by_action['list']] + except KeyError: + # action is not set return default permission_classes + return [permission() for permission in self.permission_classes] + + +class ProfileView(ModelViewSet): + serializer_class = ProfileSerializer + queryset = Profile.objects.all() + + permission_classes = [IsAuthenticated] + + def get_queryset(self): + profile = self.request.user.profile + if self.action in ['list', 'retrieve']: + return [profile] + + def get_object(self): + return self.request.user.profile + +class OffersList(ModelViewSet): + queryset = Offer.objects.all() + permission_classes = [IsAuthenticated] + serializer_class = OfferSerializer + + def get_queryset(self): + queryset = super().get_queryset() + profile = self.request.user.profile + return queryset.filter(driver = profile) + + def post(self, request, profile, *args, **kwargs): + return self.create(request, *args, **kwargs) + + +class DemandViewSet(ModelViewSet): + queryset = Demand.objects.all() + permission_classes = [IsAuthenticated] + serializer_class = DemandSerializer + + def get_queryset(self): + profile = self.request.user.profile + id = self.request.query_params.get('id') + if id: + object = Demand.objects.filter(pk = int(id)) + if object.exists(): + return object + raise NotFound(detail="Demand with that ID does not exists") + return Demand.objects.filter(passenger = profile) + + def post(self, request,profile, *args, **kwargs): + return self.create(request, *args, **kwargs) + + def destroy(self, request, *args, **kwargs): + raise MethodNotAllowed("Delete",detail="You can not delete a demand") + +class RequestBoardViewSet(ModelViewSet): + queryset = RequestBoard.objects.all() + serializer_class = RequestBoardSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self): + ''' + This function is ment to allow query params search if no query params + it returns all the available requests + ''' + demand = self.request.query_params.get('demand') + status = self.request.query_params.get('status') + offer = self.request.query_params.get('offer') + if status and demand: + options = ["PE", "AC", "DE"] + status = "PE" if 'p' in status.lower() else status + status = "AC" if 'a' in status.lower() else status + status = "DE" if 'd' in status.lower() else status + if status not in options: + raise ValidationError(detail={"error": "invalid stautus"}) + return RequestBoard.objects.filter(demand__pk=int(demand), status=status) + if demand: + return RequestBoard.objects.filter(demand__pk=int(demand)) + if offer: + return RequestBoard.objects.filter(offer__pk=int(offer)) + return RequestBoard.objects.all() + + def post(self, request, *args, **kwargs): + """ + This function is ment to allow creating of a request board + """ + profile = request.user.profile + return self.create(request, profile, *args, **kwargs) + + def put(self, request, *args, **kwargs): + """ + This function allows drivers to update the request status + """ + return self.partial_update(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + """ + This function is ment to allow cancling a request by deleteing a request + """ + return self.destroy(request, *args, **kwargs) + +class TripDetailApiView(ModelViewSet): + queryset = TripDetail.objects.all() + serializer_class = TripDetailsSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self): + print("*********** 1 ************") + offer_id = self.request.query_params.get('offer') + + if offer_id: + print("*********** 2 ************") + offer_id = int(offer_id) + return TripDetail.objects.filter(offer__id=offer_id).all() + + def retrive(self, request, *args, **kwargs): + error_message = {"status": status.HTTP_400_BAD_REQUEST, + "error": "Please pass an offer id query param", + "example": "{'offer':ID}"} + return Response(error_message, safe=False) + + def delete(self, request, *args, **kwargs): + return self.destroy(request, *args, **kwargs) + + + +class TripApiView(ModelViewSet): + queryset = Trip.objects.all() + serializer_class = TripSerializer + permission_classes = [IsAuthenticated] + + def get_queryset(self): + offer_id = self.request.query_params.get('offer') + if offer_id: + offer_id = int(offer_id) + print("*********** 1 ************") + return Trip.objects.filter(offer__id=offer_id).all() + profile = self.request.user.profile + return Trip.objects.filter(offer__driver=profile).all() + + def post(self, request, *args, **kwargs): + raise MethodNotAllowed(status.HTTP_400_METHOD_NOT_ALLOWED,detail="Post request is not allowed in this field") + # return create(self, *args, **kwargs) + +class TripChatApiView(ModelViewSet): + queryset = TripChat.objects.all() + serializer_class = TripChatSerializer + permission_classes = [IsAuthenticated] + + def retrieve(self,request,*args,**kwargs): + print(request.user.profile) + return [] + def post(self,request ,*args ,**kwargs): + mtu = self.request.user.profile + return self.create(request, mtu,*args,**kwargs) diff --git a/profile_pics/artur-aldyrkhanov-XmsxXelvq30-unsplash.jpg b/profile_pics/artur-aldyrkhanov-XmsxXelvq30-unsplash.jpg deleted file mode 100644 index 577fadf..0000000 Binary files a/profile_pics/artur-aldyrkhanov-XmsxXelvq30-unsplash.jpg and /dev/null differ