Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workshop Solutions - No merge needed #1

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"image":"mcr.microsoft.com/devcontainers/universal:2",
"customizations": {
"vscode": {
"extensions": [
"GitHub.copilot"
]
}
},
"postCreateCommand": "bash -i .devcontainer/install-dependencies.sh"

}
6 changes: 6 additions & 0 deletions .devcontainer/install-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

cd ./copilot

python -m pip install -r requirements.txt

python manage.py migrate
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
## Overview

This project is a Django-based application designed to serve as a learning platform for utilizing GitHub Copilot in software development. The application features a series of labs that guide users through the process of creating API routes, generating sample data, and testing APIs. The project aims to demonstrate the power of GitHub Copilot in streamlining development tasks and enhancing productivity.

## Getting Started
Before diving into the labs, ensure you have Django installed and the project's dependencies are set up. Navigate to the project's root directory and run the following commands:

```bash
cd copilot
python -m pip install -r requirements.txt
```

To start the server, execute:

```bash
python manage.py runserver
```

## Labs Overview

### [Lab 1: Implement New Route](./docs/001-implement-new-route.md)

In this lab, participants will learn how to add a new API route to the Django application. The new route will return a simple "Hello World" JSON response. This lab also includes writing tests for the new route to ensure it behaves as expected.

### [Lab 2: Data and Services](./docs/002-data-and-services.md)

This lab focuses on generating sample data for the application. Participants will create an API that lists Microsoft Azure VMs information, fetched from a local JSON file. This lab covers the entire flow from generating the sample data to testing the new API endpoint.

### [Lab 3: Create Homepage](/docs/003-create-homepage.md)
The details for Lab 3 are not provided in the context. However, based on the naming convention, it's likely focused on creating a homepage for the Django application, possibly involving front-end development aspects and integrating the APIs developed in previous labs.

### Testing

The project includes a suite of tests to validate the functionality of the API routes. To run the tests, ensure the server is not running and execute:

```bash
python manage.py test
```

## Conclusion

This project serves as a practical guide to leveraging GitHub Copilot in developing and testing web applications. Through a series of hands-on labs, participants will gain insights into efficient coding practices and automated testing strategies.


74 changes: 72 additions & 2 deletions copilot/api/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,73 @@
from django.test import TestCase
from rest_framework.test import APITestCase
from rest_framework.utils import json

# Create your tests here.
class TimeAPITestCase(APITestCase):
api_path = '/api/time/'

def test_get_current_time(self):
response = self.client.get(self.api_path)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertIn('time', response_data)
self.assertIsNotNone(response_data['time'])

class HelloAPITestCase(APITestCase):
api_path = '/api/hello/'

def test_get_hello(self):
response = self.client.get(self.api_path)
self.assertEqual(response.status_code, 501)
response_data = json.loads(response.content)
self.assertIn('message', response_data)
self.assertEqual(response_data['message'], 'key query parameter is required')

def test_get_hello_with_key(self):
response = self.client.get(self.api_path, {'key': 'World'})
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertIn('message', response_data)
self.assertEqual(response_data['message'], 'Hello World')


class VmsAPITestCase(APITestCase):
api_path = '/api/vms/'

def test_get_vms(self):
response = self.client.get(self.api_path)

# Check if the response status code is 200
self.assertEqual(response.status_code, 200)

# The response content should be an array
response_data = json.loads(response.content)
self.assertIsInstance(response_data, list)


# Additional tests for VmsDataAPITestCase to validate the API based on the data in vms.json

class VmsDataAPITestCase(APITestCase):
api_path = '/api/vms/'

# Existing test_get_vms method...

def test_vms_data_validation(self):
expected_vms_data = [
{"size": "Standard_D2_v3", "vcpu": 2, "memory": 8},
{"size": "Standard_D4_v3", "vcpu": 4, "memory": 16},
{"size": "Standard_D8_v3", "vcpu": 8, "memory": 32},
{"size": "Standard_D16_v3", "vcpu": 16, "memory": 64},
{"size": "Standard_D32_v3", "vcpu": 32, "memory": 128},
{"size": "Standard_D48_v3", "vcpu": 48, "memory": 192},
{"size": "Standard_D64_v3", "vcpu": 64, "memory": 256},
]

response = self.client.get(self.api_path)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)

# Ensure the response contains the correct number of VMs
self.assertEqual(len(response_data), len(expected_vms_data))

# Validate each VM's data
for vm_data in expected_vms_data:
self.assertIn(vm_data, response_data)
2 changes: 2 additions & 0 deletions copilot/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@

urlpatterns = [
path('time/', views.get_current_time),
path('hello/', views.get_hello),
path('vms/', views.get_vms),
]
28 changes: 27 additions & 1 deletion copilot/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,35 @@
from rest_framework.exceptions import NotFound
from rest_framework.parsers import JSONParser
from rest_framework.decorators import api_view
import json

import datetime

@api_view(['GET'])
def get_current_time(request):
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return JsonResponse({'time': current_time}, status=status.HTTP_200_OK)
return JsonResponse({'time': current_time}, status=status.HTTP_200_OK)


# Create a new function GET hello?key=World
# that returns a JSON {"message": "Hello World"} when the query parameter key is present
# and return HTTP 501 code with message "key query parameter is required"
# when the query parameter key is not present
@api_view(['GET'])
def get_hello(request):
key = request.GET.get('key')
if key:
return JsonResponse({'message': f'Hello {key}'}, status=status.HTTP_200_OK)
else:
return JsonResponse({'message': 'key query parameter is required'}, status=status.HTTP_501_NOT_IMPLEMENTED)


@api_view(['GET'])
def get_vms(request):
try:
with open('./data/vms.json', 'r') as file:
data = json.load(file)
return JsonResponse(data, safe=False, status=status.HTTP_200_OK)
except FileNotFoundError:
return JsonResponse({'message': 'File not found'}, status=status.HTTP_404_NOT_FOUND)

3 changes: 2 additions & 1 deletion copilot/copilot/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
Expand Down Expand Up @@ -56,7 +57,7 @@
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [os.path.join(BASE_DIR, 'copilot/templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
Expand Down
43 changes: 43 additions & 0 deletions copilot/copilot/templates/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VM List</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-5">
<h2>VM List</h2>
<table class="table">
<thead class="thead-dark">
<tr>
<th>Size</th>
<th>vCPU</th>
<th>Memory (GB)</th>
</tr>
</thead>
<tbody id="vm-list"></tbody>
</table>
</div>

<!-- Bootstrap and jQuery -->
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

<script>
$(document).ready(function() {
$.get("/api/vms/", function(data) {
var vmList = $("#vm-list");
data.forEach(function(vm, index) {
var rowClass = index % 2 === 0 ? "table-primary" : "table-secondary";
vmList.append(
`<tr class="${rowClass}"><td>${vm.size}</td><td>${vm.vcpu}</td><td>${vm.memory}</td></tr>`
);
});
});
});
</script>
</body>
</html>
12 changes: 0 additions & 12 deletions copilot/copilot/tests.py
Original file line number Diff line number Diff line change
@@ -1,12 +0,0 @@
from rest_framework.test import APITestCase
from rest_framework.utils import json

class TimeAPITestCase(APITestCase):
api_path = '/api/time/'

def test_get_current_time(self):
response = self.client.get(self.api_path)
self.assertEqual(response.status_code, 200)
response_data = json.loads(response.content)
self.assertIn('time', response_data)
self.assertIsNotNone(response_data['time'])
2 changes: 2 additions & 0 deletions copilot/copilot/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
from django.urls import path
from django.urls.conf import include
from api import urls
from .views import home

urlpatterns = [
path('', home),
path('api/', include(urls)),
path('admin/', admin.site.urls),
]
4 changes: 4 additions & 0 deletions copilot/copilot/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from django.shortcuts import render

def home(request):
return render(request, 'home.html')
37 changes: 37 additions & 0 deletions copilot/data/vms.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"size": "Standard_D2_v3",
"vcpu": 2,
"memory": 8
},
{
"size": "Standard_D4_v3",
"vcpu": 4,
"memory": 16
},
{
"size": "Standard_D8_v3",
"vcpu": 8,
"memory": 32
},
{
"size": "Standard_D16_v3",
"vcpu": 16,
"memory": 64
},
{
"size": "Standard_D32_v3",
"vcpu": 32,
"memory": 128
},
{
"size": "Standard_D48_v3",
"vcpu": 48,
"memory": 192
},
{
"size": "Standard_D64_v3",
"vcpu": 64,
"memory": 256
}
]
4 changes: 4 additions & 0 deletions copilot/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
asgiref==3.8.1
Django==5.0.6
djangorestframework==3.15.1
sqlparse==0.5.0
Loading