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

Cache #61

Merged
merged 6 commits into from
Nov 28, 2023
Merged
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
63 changes: 63 additions & 0 deletions .github/workflows/main_restaurantreview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy
# More GitHub Actions for Azure: https://github.com/Azure/actions
# More info on Python, GitHub Actions, and Azure App Service: https://aka.ms/python-webapps-actions

name: Build and deploy Python app to Azure Web App - restaurantReview

on:
push:
branches:
- main
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2

- name: Set up Python version
uses: actions/setup-python@v1
with:
python-version: '3.11'

- name: Create and start virtual environment
run: |
python -m venv venv
source venv/bin/activate

- name: Install dependencies
run: pip install -r requirements.txt

# Optional: Add step to run tests here (PyTest, Django test suites, etc.)

- name: Upload artifact for deployment jobs
uses: actions/upload-artifact@v2
with:
name: python-app
path: |
.
!venv/

deploy:
runs-on: ubuntu-latest
needs: build
environment:
name: 'Production'
url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}

steps:
- name: Download artifact from build job
uses: actions/download-artifact@v2
with:
name: python-app
path: .

- name: 'Deploy to Azure Web App'
uses: azure/webapps-deploy@v2
id: deploy-to-webapp
with:
app-name: 'restaurantReview'
slot-name: 'Production'
publish-profile: ${{ secrets.AZUREAPPSERVICE_PUBLISHPROFILE_0EB1DB70D6D34D01BE7BA033F7890571 }}
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

This is a Python web app using the Django framework and the Azure Database for PostgreSQL relational database service. The Django app is hosted in a fully managed Azure App Service. This app is designed to be be run locally and then deployed to Azure. You can either deploy this project by following the tutorial [*Deploy a Python (Django or Flask) web app with PostgreSQL in Azure*](https://docs.microsoft.com/azure/app-service/tutorial-python-postgresql-app) or by using the [Azure Developer CLI (azd)](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview) according to the instructions below.

## The Cache branch
The Cache branch in the repository contains sample code to save web response out and user session data in a Redis Cache. For web response caching, if a user clicks into the detailed view of a resturant, the details page will be cached for 60 seconds. This increases the application performance by saving server resource and reducing dependency calls into the PostgreSQL. For user session data such as last viewed restaurant, saving in Redis Cache improves consistency and resiliency comparing with in-memory.
Files changed in the Cache branch:
* ./azureproject/production.py and settings.py - added cache and session middlewares
* ./restaurant_review/views.py - added @cache_page tag and session data "lastViewedRestaurant"
* ./restaurant_review/templates/restaurant_review/index.html - added display for 'LastViewedRestaurant'

## Requirements

The [requirements.txt](./requirements.txt) has the following packages:
Expand Down
13 changes: 13 additions & 0 deletions azureproject/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

Expand All @@ -38,3 +39,15 @@
'PASSWORD': conn_str_params['password'],
}
}

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.environ.get('CACHELOCATION'),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
'PASSWORD': os.environ.get('CACHEKEY')
},
}
}
11 changes: 11 additions & 0 deletions azureproject/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
ROOT_URLCONF = 'azureproject.urls'

TEMPLATES = [
Expand Down Expand Up @@ -119,6 +120,16 @@
},
]

CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": os.environ.get('CACHELOCATION'),
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
'PASSWORD': os.environ.get('CACHEKEY')
},
}
}

# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/
Expand Down
101 changes: 99 additions & 2 deletions infra/resources.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,16 @@ param secretKey string
var prefix = '${name}-${resourceToken}'

var pgServerName = '${prefix}-postgres-server'
//added for Redis Cache
var cacheServerName = '${prefix}-redisCache'
var databaseSubnetName = 'database-subnet'
var webappSubnetName = 'webapp-subnet'
//added for Redis Cache
var cacheSubnetName = 'cache-subnet'
//added for Redis Cache
var cachePrivateEndpointName = 'cache-privateEndpoint'
//added for Redis Cache
var cachePvtEndpointDnsGroupName = 'cacheDnsGroup'

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
name: '${prefix}-vnet'
Expand Down Expand Up @@ -52,6 +60,12 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
]
}
}
{
name: cacheSubnetName
properties:{
addressPrefix: '10.0.2.0/24'
}
}
]
}
resource databaseSubnet 'subnets' existing = {
Expand All @@ -60,6 +74,10 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
resource webappSubnet 'subnets' existing = {
name: webappSubnetName
}
//added for Redis Cache
resource cacheSubnet 'subnets' existing = {
name: cacheSubnetName
}
}

resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
Expand All @@ -71,6 +89,16 @@ resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
]
}

// added for Redis Cache
resource privateDnsZoneCache 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.redis.cache.windows.net'
location: 'global'
tags: tags
dependsOn:[
virtualNetwork
]
}

resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZone
name: '${pgServerName}-link'
Expand All @@ -83,6 +111,54 @@ resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLin
}
}

//added for Redis Cache
resource privateDnsZoneLinkCache 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZoneCache
name: 'privatelink.redis.cache.windows.net-applink'
location: 'global'
properties: {
registrationEnabled: false
virtualNetwork: {
id: virtualNetwork.id
}
}
}


resource cachePrivateEndpoint 'Microsoft.Network/privateEndpoints@2023-05-01' = {
name: cachePrivateEndpointName
location: location
properties: {
subnet: {
id: virtualNetwork::cacheSubnet.id
}
privateLinkServiceConnections: [
{
name: cachePrivateEndpointName
properties: {
privateLinkServiceId: redisCache.id
groupIds: [
'redisCache'
]
}
}
]
}
resource cachePvtEndpointDnsGroup 'privateDnsZoneGroups' = {
name: cachePvtEndpointDnsGroupName
properties: {
privateDnsZoneConfigs: [
{
name: 'privatelink-redis-cache-windows-net'
properties: {
privateDnsZoneId: privateDnsZoneCache.id
}
}
]
}
}
}

resource web 'Microsoft.Web/sites@2022-03-01' = {
name: '${prefix}-app-service'
location: location
Expand All @@ -92,7 +168,7 @@ resource web 'Microsoft.Web/sites@2022-03-01' = {
serverFarmId: appServicePlan.id
siteConfig: {
alwaysOn: true
linuxFxVersion: 'PYTHON|3.10'
linuxFxVersion: 'PYTHON|3.11'
ftpsState: 'Disabled'
appCommandLine: 'startup.sh'
}
Expand All @@ -108,6 +184,9 @@ resource web 'Microsoft.Web/sites@2022-03-01' = {
AZURE_POSTGRESQL_CONNECTIONSTRING: 'dbname=${djangoDatabase.name} host=${postgresServer.name}.postgres.database.azure.com port=5432 sslmode=require user=${postgresServer.properties.administratorLogin} password=${databasePassword}'
SCM_DO_BUILD_DURING_DEPLOYMENT: 'true'
SECRET_KEY: secretKey
//TODO: add settings for Redis Cache
CACHELOCATION: 'rediss://${redisCache.name}.redis.cache.windows.net:6380/0'
CACHEKEY: redisCache.listKeys().primaryKey
}
}

Expand Down Expand Up @@ -151,7 +230,7 @@ resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
location: location
tags: tags
sku: {
name: 'B1'
name: 'S1'
}
properties: {
reserved: true
Expand Down Expand Up @@ -229,5 +308,23 @@ resource djangoDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@202
name: 'django'
}

//added for Redis Cache
resource redisCache 'Microsoft.Cache/redis@2023-04-01' = {
location:location
name:cacheServerName
properties:{
sku:{
capacity: 1
family:'C'
name:'Standard'
}
enableNonSslPort:false
redisVersion:'6'
publicNetworkAccess:'Disabled'
}


}

output WEB_URI string = 'https://${web.properties.defaultHostName}'
output APPLICATIONINSIGHTS_CONNECTION_STRING string = applicationInsightsResources.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Django==4.2.5
psycopg2-binary==2.9.7
python-dotenv==1.0.0
whitenoise==6.5.0
whitenoise==6.5.0
django-redis==5.3.0
3 changes: 3 additions & 0 deletions restaurant_review/templates/restaurant_review/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
</style>
{% endblock %}
{% block content %}
{% if LastViewedRestaurant %}
<h3>Last viewed restaurant: {{ LastViewedRestaurant }}</h3>
{% endif %}
<h1>Restaurants</h1>

{% if restaurants %}
Expand Down
8 changes: 6 additions & 2 deletions restaurant_review/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.cache import cache_page

from restaurant_review.models import Restaurant, Review

Expand All @@ -12,12 +13,15 @@
def index(request):
print('Request for index page received')
restaurants = Restaurant.objects.annotate(avg_rating=Avg('review__rating')).annotate(review_count=Count('review'))
return render(request, 'restaurant_review/index.html', {'restaurants': restaurants})

lastViewedRestaurant = request.session.get("lastViewedRestaurant", False)
print(lastViewedRestaurant)
return render(request, 'restaurant_review/index.html', {'LastViewedRestaurant': lastViewedRestaurant, 'restaurants': restaurants})

@cache_page(60)
def details(request, id):
print('Request for restaurant details page received')
restaurant = get_object_or_404(Restaurant, pk=id)
request.session["lastViewedRestaurant"] = restaurant.name
return render(request, 'restaurant_review/details.html', {'restaurant': restaurant})


Expand Down
Loading