diff --git a/README.md b/README.md index 2a8d72d..96731cc 100644 --- a/README.md +++ b/README.md @@ -28,4 +28,5 @@ Without further ado, let's get started. 1. [Story](docs/1_Story) 2. [Prerequisites and development environment setup](docs/2_Prerequisites) 3. [Getting started - app frontend and backend creation](docs/3_GettingStarted) -4. [MongoDB install and setup](docs/4_MongoDBInstallSetup/) +4. [OctoFit Tracker database and app backend setup](docs/4_BackendSettings) +5. [Populate the database with sample data](docs/5_PopulateDBwData) diff --git a/docs/1_Story/README.md b/docs/1_Story/README.md index 4f9ae7f..f01fc16 100644 --- a/docs/1_Story/README.md +++ b/docs/1_Story/README.md @@ -30,10 +30,18 @@ OctoFit Tracker will include: GitHub Copilot and Copilot Chat uses OpenAI GPT models for its coding suggestions and chat interaction. OpenAI gpt-4o: *"Our high-intelligence flagship model for complex, multi-step tasks. GPT-4o is cheaper and faster than GPT-4 Turbo. Currently points to gpt-4o-2024-08-06"* -[OpenAI GPT models explained](https://platform.openai.com/docs/models) +#### [OpenAI GPT models explained](https://platform.openai.com/docs/models) ![openai gpt models](./gpt-models.png) +#### Prompt engineering + +- [GitHub documentation prompt engineering](https://docs.github.com/en/copilot/using-github-copilot/prompt-engineering-for-github-copilot) +- [How to use GitHub Copilot: Prompts, tips, and use cases](https://github.blog/2023-06-20-how-to-write-better-prompts-for-github-copilot/) +- [Using GitHub Copilot in your IDE: Tips, tricks, and best practices](https://github.blog/2024-03-25-how-to-use-github-copilot-in-your-ide-tips-tricks-and-best-practices/) +- [A developer’s guide to prompt engineering and LLMs](https://docs.github.com/en/copilot/using-github-copilot/prompt-engineering-for-github-copilot#:~:text=A%20developer%E2%80%99s%20guide%20to%20prompt%20engineering%20and%20LLMs) +- [Prompting GitHub Copilot Chat to become your personal AI assistant for accessibility]https://github.blog/2023-10-09-prompting-github-copilot-chat-to-become-your-personal-ai-assistant-for-accessibility/() + OpenAI gpt-4o: *"Our high-intelligence flagship model for complex, multi-step tasks. GPT-4o is cheaper and faster than GPT-4 Turbo. Currently points to gpt-4o-2024-08-06"* > NOTE: we will be using gpt-4o in GitHub Copilot Chat for this workshop at GitHub Universe. diff --git a/docs/3_GettingStarted/README.md b/docs/3_GettingStarted/README.md index 358c017..fad6aad 100644 --- a/docs/3_GettingStarted/README.md +++ b/docs/3_GettingStarted/README.md @@ -26,7 +26,7 @@ generate instructions in this order 1. Create the frontend and backend in the octofit-tracker directory of this repository in one command 2. Setup backend python venv and install octofit-tracker/requirements.txt first -3. The octofit-tracker/backend directory will store the django project with the name octofit-tracker +3. The octofit-tracker/backend directory will store the django project and app with the name octofit-tracker 4. The Django project octofit-tracker directory will have all the backend components for the app 5. Create the django app directly in the directory octofit_tracker/backend 6. Setup the octofit-tracker/frontend directory will store the react app with no subdirectories @@ -75,4 +75,4 @@ Important to avoid using public code and we do NOT need to initialize the git re ![octofit-tracker app directory tree](./3_3_OctoFitTrackerDirTree.png)
-[Back :: Previous: Prerequisites and development environment setup](../2_Prerequisites) | [Next :: OctoFit app backend setup](../4_BackendSettings/) +[Back :: Previous: Prerequisites and development environment setup](../2_Prerequisites) | [Next :: OctoFit Tracker database and app backend setup](../4_BackendSettings/) diff --git a/docs/4_BackendSettings/README.md b/docs/4_BackendSettings/README.md index e337338..2b780aa 100644 --- a/docs/4_BackendSettings/README.md +++ b/docs/4_BackendSettings/README.md @@ -1,16 +1,19 @@ -# Setup database in settings.py, models, views, serializers, and populate data +# OctoFit Tracker database and app backend setup -## Use Copilot Chat +## Initialize the database, setup database and install apps in settings.py, models, serializers, urls, and views Type the following prompt in GitHub Copilot Chat: ```text In our next steps lets think step by step and setup the following in this order -1. Initialize the mongo octofit_db database -2. settings.py in our django project for mongodb octofit_db database including localhost and the port -3. settings.py in our django project setup for all installed apps. ex djongo, octofit_tracker, rest_framework -4. In octofit_tracker project setup and touch models, serializers, urls, and views for users, teams, activity, leaderboard, and workouts +1. Initialize the mongo octofit_db database and create a correct table structure for users, teams, activities, leaderboards, and workouts collections +2. Make sure there is a unique id for primary key for the user collection + ex. db.users.createIndex({ "email": 1 }, { unique: true }) +3. settings.py in our django project for mongodb octofit_db database including localhost and the port +4. settings.py in our django project setup for all installed apps. ex djongo, octofit_tracker, rest_framework +5. In octofit_tracker project setup and use command touch models.py, serializers.py, urls.py, and views.py for users, teams, activities, leaderboards, and workouts +6. make sure urls.py has a root, admin, and api endpoints ``` ![OctoFit Tracker backend files](./4_1_OctoFitTrackerBackendFiles.png)
@@ -56,41 +59,34 @@ INSTALLED_APPS = [ ```python # FILE: octofit-tracker/backend/octofit_tracker/models.py -from django.db import models -from django.contrib.auth.models import AbstractUser +from djongo import models -class User(AbstractUser): +class User(models.Model): + _id = models.ObjectIdField() + username = models.CharField(max_length=100) email = models.EmailField(unique=True) - groups = models.ManyToManyField( - 'auth.Group', - related_name='octofit_tracker_user_set', - blank=True, - help_text='The groups this user belongs to.', - verbose_name='groups', - ) - user_permissions = models.ManyToManyField( - 'auth.Permission', - related_name='octofit_tracker_user_set', - blank=True, - help_text='Specific permissions for this user.', - verbose_name='user permissions', - ) + password = models.CharField(max_length=100) class Team(models.Model): + _id = models.ObjectIdField() name = models.CharField(max_length=100) + members = models.ArrayReferenceField(to=User, on_delete=models.CASCADE) class Activity(models.Model): + _id = models.ObjectIdField() + user = models.ForeignKey(User, on_delete=models.CASCADE) activity_type = models.CharField(max_length=100) - -class Workout(models.Model): - name = models.CharField(max_length=100) duration = models.DurationField() - activity = models.ForeignKey(Activity, on_delete=models.CASCADE) - user = models.ForeignKey(User, on_delete=models.CASCADE) class Leaderboard(models.Model): + _id = models.ObjectIdField() user = models.ForeignKey(User, on_delete=models.CASCADE) score = models.IntegerField() + +class Workout(models.Model): + _id = models.ObjectIdField() + name = models.CharField(max_length=100) + description = models.TextField() ``` #### serializers.py @@ -99,7 +95,7 @@ class Leaderboard(models.Model): # FILE: octofit-tracker/backend/octofit_tracker/serializers.py from rest_framework import serializers -from .models import User, Team, Activity, Workout, Leaderboard +from .models import User, Team, Activity, Leaderboard, Workout class UserSerializer(serializers.ModelSerializer): class Meta: @@ -116,14 +112,14 @@ class ActivitySerializer(serializers.ModelSerializer): model = Activity fields = '__all__' -class WorkoutSerializer(serializers.ModelSerializer): +class LeaderboardSerializer(serializers.ModelSerializer): class Meta: - model = Workout + model = Leaderboard fields = '__all__' -class LeaderboardSerializer(serializers.ModelSerializer): +class WorkoutSerializer(serializers.ModelSerializer): class Meta: - model = Leaderboard + model = Workout fields = '__all__' ``` @@ -132,9 +128,9 @@ class LeaderboardSerializer(serializers.ModelSerializer): ```python # FILE: octofit_tracker/views.py -from rest_framework import viewsets -from .models import User, Team, Activity, Leaderboard, Workout from .serializers import UserSerializer, TeamSerializer, ActivitySerializer, LeaderboardSerializer, WorkoutSerializer +from rest_framework.response import Response +from rest_framework.decorators import api_view class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() @@ -155,6 +151,17 @@ class LeaderboardViewSet(viewsets.ModelViewSet): class WorkoutViewSet(viewsets.ModelViewSet): queryset = Workout.objects.all() serializer_class = WorkoutSerializer + +@api_view(['GET']) +def api_root(request, format=None): + base_url = 'http://upgraded-space-happiness-959pr7vpgw3p7vv-8000.app.github.dev/' + return Response({ + 'users': base_url + 'api/users/?format=api', + 'teams': base_url + 'api/teams/?format=api', + 'activities': base_url + 'api/activities/?format=api', + 'leaderboards': base_url + 'api/leaderboards/?format=api', + 'workouts': base_url + 'api/workouts/?format=api' + }) ``` #### urls.py @@ -165,7 +172,7 @@ class WorkoutViewSet(viewsets.ModelViewSet): from django.contrib import admin from django.urls import path, include from rest_framework.routers import DefaultRouter -from .views import UserViewSet, TeamViewSet, ActivityViewSet, LeaderboardViewSet, WorkoutViewSet +from .views import UserViewSet, TeamViewSet, ActivityViewSet, LeaderboardViewSet, WorkoutViewSet, api_root router = DefaultRouter() router.register(r'users', UserViewSet) @@ -175,8 +182,9 @@ router.register(r'leaderboards', LeaderboardViewSet) router.register(r'workouts', WorkoutViewSet) urlpatterns = [ - path('admin/', admin.site.urls), - path('api/', include(router.urls)), + path('', api_root, name='api-root'), # Root endpoint + path('admin/', admin.site.urls), # Admin endpoint + path('api/', include(router.urls)), # API endpoint ] ``` diff --git a/docs/5_PopulateDBwData/README.md b/docs/5_PopulateDBwData/README.md index 39e0f5f..a86393d 100644 --- a/docs/5_PopulateDBwData/README.md +++ b/docs/5_PopulateDBwData/README.md @@ -5,7 +5,7 @@ > NOTE: This is an example of not being specific skip this prompt and go to the next one ```text -Let's use manage.py to get everything setup we need to create init.py for populate_db command include steps to migrate as well +Let's use manage.py to get everything setup we need to populate_db command include steps to migrate as well ``` ### Example of not being specfic in prompting Copilot Chat @@ -19,10 +19,11 @@ Let's use manage.py to get everything setup we need to create init.py for popula Type the following prompt in GitHub Copilot Chat: ```text -Let's use manage.py to get the database setup and populated +Let's use manage.py to get the database setup and populated based on fields in models.py -- Populate the database with super hero users -- Create full setup for a command populate_db.py +- Create populate_db.py so it initializes and deletes previous data and recreates it +- populate_db.py creates users, teams, activities, leaderboards, and workouts +- users will be super hero users - Include steps to migrate in the octofit_tracker project ``` @@ -33,29 +34,112 @@ Let's use manage.py to get the database setup and populated ```python # FILE: octofit-tracker/backend/octofit_tracker/management/commands/populate_db.py +# octofit_tracker/management/commands/populate_db.py + from django.core.management.base import BaseCommand -from octofit_tracker.models import User, Team, Activity, Workout, Leaderboard +from octofit_tracker.models import User, Team, Activity, Leaderboard, Workout +from django.conf import settings +from pymongo import MongoClient from datetime import timedelta class Command(BaseCommand): - help = 'Populate the database with initial data' + help = 'Populate the database with superhero users, teams, activities, leaderboards, and workouts' def handle(self, *args, **kwargs): - # Create Superhero Users + # Connect to MongoDB + client = MongoClient(settings.DATABASES['default']['HOST'], settings.DATABASES['default']['PORT']) + db = client[settings.DATABASES['default']['NAME']] + + # Drop existing collections + db.users.drop() + db.teams.drop() + db.activities.drop() + db.leaderboards.drop() + db.workouts.drop() + + # Populate users users = [ {'username': 'superman', 'email': 'superman@heroes.com', 'password': 'superpassword'}, {'username': 'batman', 'email': 'batman@heroes.com', 'password': 'batpassword'}, {'username': 'wonderwoman', 'email': 'wonderwoman@heroes.com', 'password': 'wonderpassword'}, + {'username': 'flash', 'email': 'flash@heroes.com', 'password': 'flashpassword'}, + {'username': 'aquaman', 'email': 'aquaman@heroes.com', 'password': 'aquapassword'}, ] + user_objects = [] for user_data in users: - user, created = User.objects.get_or_create(**user_data) + user, created = User.objects.get_or_create(email=user_data['email'], defaults=user_data) + user_objects.append(user) if created: - self.stdout.write(self.style.SUCCESS(f"User {user.username} created")) + self.stdout.write(self.style.SUCCESS(f'Successfully created user {user.username}')) + else: + self.stdout.write(self.style.WARNING(f'User {user.username} already exists')) + + # Ensure all user objects are saved + for user in user_objects: + user.save() - # Add more data population logic here if needed + # Populate teams + teams = [ + {'name': 'Justice League', 'members': [user_objects[0], user_objects[1], user_objects[2], user_objects[3], user_objects[4]]}, + ] - self.stdout.write(self.style.SUCCESS('Database populated successfully')) + for team_data in teams: + team, created = Team.objects.get_or_create(name=team_data['name']) + if created: + for member in team_data['members']: + team.members.add(member) + self.stdout.write(self.style.SUCCESS(f'Successfully created team {team.name}')) + else: + self.stdout.write(self.style.WARNING(f'Team {team.name} already exists')) + + # Populate activities + activities = [ + {'user': user_objects[0], 'activity_type': 'Flying', 'duration': timedelta(hours=1)}, + {'user': user_objects[1], 'activity_type': 'Martial Arts', 'duration': timedelta(hours=2)}, + {'user': user_objects[2], 'activity_type': 'Training', 'duration': timedelta(hours=1, minutes=30)}, + {'user': user_objects[3], 'activity_type': 'Running', 'duration': timedelta(minutes=30)}, + {'user': user_objects[4], 'activity_type': 'Swimming', 'duration': timedelta(hours=1, minutes=15)}, + ] + + for activity_data in activities: + activity, created = Activity.objects.get_or_create(**activity_data) + if created: + self.stdout.write(self.style.SUCCESS(f'Successfully created activity for {activity.user.username}')) + else: + self.stdout.write(self.style.WARNING(f'Activity for {activity.user.username} already exists')) + + # Populate leaderboards + leaderboards = [ + {'user': user_objects[0], 'score': 100}, + {'user': user_objects[1], 'score': 90}, + {'user': user_objects[2], 'score': 95}, + {'user': user_objects[3], 'score': 85}, + {'user': user_objects[4], 'score': 80}, + ] + + for leaderboard_data in leaderboards: + leaderboard, created = Leaderboard.objects.get_or_create(**leaderboard_data) + if created: + self.stdout.write(self.style.SUCCESS(f'Successfully created leaderboard entry for {leaderboard.user.username}')) + else: + self.stdout.write(self.style.WARNING(f'Leaderboard entry for {leaderboard.user.username} already exists')) + + # Populate workouts + workouts = [ + {'name': 'Super Strength Training', 'description': 'Training for super strength'}, + {'name': 'Martial Arts Training', 'description': 'Training for martial arts'}, + {'name': 'Amazonian Training', 'description': 'Training for Amazonian warriors'}, + {'name': 'Speed Training', 'description': 'Training for super speed'}, + {'name': 'Aquatic Training', 'description': 'Training for underwater activities'}, + ] + + for workout_data in workouts: + workout, created = Workout.objects.get_or_create(**workout_data) + if created: + self.stdout.write(self.style.SUCCESS(f'Successfully created workout {workout.name}')) + else: + self.stdout.write(self.style.WARNING(f'Workout {workout.name} already exists')) ``` ![Migrate and populate db](./5_3_MigratePopulateDb.png)