diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 00000000..15147eb6 --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,9 @@ +# .devcontainer directory + +This `.devcontainer` directory contains the configuration for a [dev container](https://docs.github.com/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers) and isn't used by the sample application. + +The dev container configuration lets you open the repository in a [GitHub codespace](https://docs.github.com/codespaces/overview) or a dev container in Visual Studio Code. For your convenience, the dev container is configured with the following: + +- Python +- PostgreSQL +- [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview) (so you can run `azd` commands directly). diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3d0a8cb6..897fc0ab 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,11 +1,11 @@ { - "name": "msdocs-django-postgresql-sample-app", + "name": "msdocs-flask-postgresql-sample-app", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspace", "features": { "ghcr.io/azure/azure-dev/azd:latest": {} - }, + }, "customizations": { "vscode": { // Add the IDs of extensions you want installed when the container is created. @@ -51,9 +51,9 @@ } } }, - // Use 'forwardPorts' to make a list of ports inside the container available locally. + // Use 'forwardPorts' to make a list of ports inside the container available locally. 5000 is for Flask, and 5432 is for PostgreSQL. "forwardPorts": [ - 8000, 5432 + 5000, 5432 ], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "", diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index a447e913..21855366 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -21,12 +21,16 @@ services: volumes: - postgres-data:/var/lib/postgresql/data environment: - POSTGRES_USER: app_user POSTGRES_DB: app + POSTGRES_USER: app_user POSTGRES_PASSWORD: app_password # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. # (Adding the "ports" property to this file will not forward from a Codespace.) + redis: + image: redis + restart: unless-stopped + volumes: postgres-data: diff --git a/.env.sample b/.env.sample index 0c52cfbe..a626555a 100644 --- a/.env.sample +++ b/.env.sample @@ -1,5 +1,6 @@ -DBNAME= +DBNAME= DBHOST= DBUSER= DBPASS= -SECRET_KEY= \ No newline at end of file +CACHELOCATION= +SECRET_KEY= \ No newline at end of file diff --git a/.env.sample.devcontainer b/.env.sample.devcontainer index 9b49b915..7e893174 100644 --- a/.env.sample.devcontainer +++ b/.env.sample.devcontainer @@ -2,4 +2,5 @@ DBNAME=app DBHOST=localhost DBUSER=app_user DBPASS=app_password -SECRET_KEY=secret_key \ No newline at end of file +CACHELOCATION=redis://redis:6379/0 +SECRET_KEY=secret_key diff --git a/.github/workflows/azure-dev.yaml b/.github/workflows/azure-dev.yaml deleted file mode 100644 index 2cc1938e..00000000 --- a/.github/workflows/azure-dev.yaml +++ /dev/null @@ -1,61 +0,0 @@ -name: Azure Developer CLI - -on: - workflow_dispatch: - push: - branches: - - main - -permissions: - id-token: write - contents: read - -jobs: - build: - runs-on: ubuntu-latest - container: - image: mcr.microsoft.com/azure-dev-cli-apps:latest - env: - AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} - AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} - AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Log in with Azure (Federated Credentials) - if: ${{ env.AZURE_CLIENT_ID != '' }} - run: | - azd login ` - --client-id "$Env:AZURE_CLIENT_ID" ` - --federated-credential-provider "github" ` - --tenant-id "$Env:AZURE_TENANT_ID" - shell: pwsh - - - name: Log in with Azure (Client Credentials) - if: ${{ env.AZURE_CREDENTIALS != '' }} - run: | - $info = $Env:AZURE_CREDENTIALS | ConvertFrom-Json -AsHashtable; - Write-Host "::add-mask::$($info.clientSecret)" - azd login ` - --client-id "$($info.clientId)" ` - --client-secret "$($info.clientSecret)" ` - --tenant-id "$($info.tenantId)" - shell: pwsh - env: - AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} - - - name: Azure Dev Provision - run: azd provision --no-prompt - env: - AZURE_ENV_NAME: ${{ secrets.AZURE_ENV_NAME }} - AZURE_LOCATION: ${{ secrets.AZURE_LOCATION }} - AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: Azure Dev Deploy - run: azd deploy --no-prompt - env: - AZURE_ENV_NAME: ${{ secrets.AZURE_ENV_NAME }} - AZURE_LOCATION: ${{ secrets.AZURE_LOCATION }} - AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} diff --git a/.vscode/README.md b/.vscode/README.md new file mode 100644 index 00000000..35976b45 --- /dev/null +++ b/.vscode/README.md @@ -0,0 +1,3 @@ +# .vscode directory + +This `.vscode` directory contains configuration that lets you launch and debug in Visual Studio Code and isn't used by the sample application. diff --git a/README.md b/README.md index 7f327836..2259a65e 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ 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. +Additionally, the sample application demonstrates Azure Redis Cache access by caching the restaurant details page for 60 seconds. You can add the Azure Redis Cache integration in the [secure-by-default web app + database creation wizard](https://portal.azure.com/?feature.customportal=false#create/Microsoft.AppServiceWebAppDatabaseV3), and it's also included in the [AZD template](https://github.com/Azure-Samples/python-app-service-postgresql-infra). + ## Requirements -The [requirements.txt](./requirements.txt) has the following packages: +The [requirements.txt](./requirements.txt) has the following packages, all used by a typical data-driven Django application: | Package | Description | | ------- | ----------- | @@ -12,60 +14,49 @@ The [requirements.txt](./requirements.txt) has the following packages: | [pyscopg2-binary](https://pypi.org/project/psycopg-binary/) | PostgreSQL database adapter for Python. | | [python-dotenv](https://pypi.org/project/python-dotenv/) | Read key-value pairs from .env file and set them as environment variables. In this sample app, those variables describe how to connect to the database locally.

This package is used in the [manage.py](./manage.py) file to load environment variables. | | [whitenoise](https://pypi.org/project/whitenoise/) | Static file serving for WSGI applications, used in the deployed app.

This package is used in the [azureproject/production.py](./azureproject/production.py) file, which configures production settings. | +| [django-redis](https://pypi.org/project/django-redis/) | Redis cache backend for Django. | -## Using this project with the Azure Developer CLI (azd) +## Run the sample -This project is designed to work well with the [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview), -which makes it easier to develop apps locally, deploy them to Azure, and monitor them. +This project has a [dev container configuration](.devcontainer/), which makes it easier to develop apps locally, deploy them to Azure, and monitor them. The easiest way to run this sample application is inside a GitHub codespace. Follow these steps: -### Local development +1. Fork this repository to your account. For instructions, see [Fork a repo](https://docs.github.com/get-started/quickstart/fork-a-repo). -This project has Dev Container support, so you can open it in Github Codespaces or local VS Code with the Dev Containers extension. If you're unable to open the Dev Container, -then it's best to first [create a Python virtual environment](https://docs.python.org/3/tutorial/venv.html#creating-virtual-environments) and activate that. +1. From the repository root of your fork, select **Code** > **Codespaces** > **+**. -1. Install the requirements: +1. In the codespace terminal, run the following commands: ```shell + # Install requirements python3 -m pip install -r requirements.txt - ``` - -2. Create an `.env` file using `.env.sample` as a guide. Set the value of `DBNAME` to the name of an existing database in your local PostgreSQL instance. Set the values of `DBHOST`, `DBUSER`, and `DBPASS` as appropriate for your local PostgreSQL instance. If you're in the Dev Container, copy the values from `.env.sample.devcontainer`. - -3. In the `.env` file, fill in a secret value for `SECRET_KEY`. You can use this command to generate an appropriate value: - - ```shell - python -c 'import secrets; print(secrets.token_hex())' - ``` - -4. Run the migrations: (or use VS Code "Run" button and select "Migrate") - - ```shell + # Create .env with environment variables + cp .env.sample.devcontainer .env + # Run database migrations python3 manage.py migrate - ``` - -5. Run the local server: (or use VS Code "Run" button and select "Run server") - - ```shell + # Start the development server python3 manage.py runserver ``` -### Deployment +1. When you see the message `Your application running on port 8000 is available.`, click **Open in Browser**. -This repo is set up for deployment on Azure App Service (w/PostGreSQL server) using the configuration files in the `infra` folder. +### Quick deploy -🎥 Watch a deployment of the code in [this screencast](https://www.youtube.com/watch?v=JDlZ4TgPKYc). +This project is designed to work well with the [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/overview), which makes it easier to develop apps locally, deploy them to Azure, and monitor them. + +🎥 Watch a deployment of the code in [this screencast](https://www +.youtube.com/watch?v=JDlZ4TgPKYc). Steps for deployment: 1. Sign up for a [free Azure account](https://azure.microsoft.com/free/) -2. Install the [Azure Dev CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd). (If you opened this repository in a Dev Container, that part will be done for you.) +2. Install the [Azure Dev CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd). (If you opened this repository in a Dev Container, it's already installed for you.) 3. Initialize a new `azd` environment: ```shell azd init ``` - It will prompt you to provide a name (like "django-app") that will later be used in the name of the deployed resources. + It will prompt you to provide a name (like "django-app"), which will later be used in the name of the deployed resources. 4. Provision and deploy all the resources: @@ -91,25 +82,6 @@ Steps for deployment: azd deploy ``` -### CI/CD pipeline - -This project includes a Github workflow for deploying the resources to Azure -on every push. That workflow requires several Azure-related authentication secrets to be stored as Github action secrets. To set that up, run: - -```shell -azd pipeline config -``` - -### Monitoring - -The deployed resources include a Log Analytics workspace with an Application Insights dashboard to measure metrics like server response time. - -To open that dashboard, just run: - -```shell -azd monitor --overview -``` - ## Getting help If you're working with this project and running into issues, please post in [Issues](/issues). diff --git a/azure.yaml b/azure.yaml index 34ddec64..10bffe50 100644 --- a/azure.yaml +++ b/azure.yaml @@ -1,10 +1,34 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json +# azure.yaml is an azd configuration file and isn't used by the sample application. -name: django-postgresql-sample-app +name: python-app-service-postgresql-redis-infra metadata: - template: django-postgresql-sample-app@0.0.1-beta + template: python-app-service-postgresql-redis-infra@0.0.1-beta services: web: project: . language: py host: appservice +hooks: + postprovision: + posix: + shell: sh + run: echo $'\n\nApp Service app has the following settings:\n' && echo "$WEB_APP_SETTINGS" | jq -r '.[]' | sed 's/\(.*\)/\t- \1/' && echo -e $"\nSee the settings in the portal:\033[1;36m $WEB_APP_CONFIG" + interactive: true + continueOnError: true + windows: + shell: pwsh + run: Write-Host "`n`nApp Service app has the following settings:`n" $WEB_APP_SETTINGS | ConvertFrom-Json | ForEach-Object { Write-Host "\t- $_" } + interactive: true + continueOnError: true + postdeploy: + posix: + shell: sh + run: echo -e $"\n\nOpen SSH session to App Service container at:\033[1;36m $WEB_APP_SSH\033[0m" && echo -e $"Stream App Service logs at:\033[1;36m $WEB_APP_LOG_STREAM" + interactive: true + continueOnError: true + windows: + shell: pwsh + run: Write-Host "`n`nOpen SSH session to App Service container at:`n" $WEB_APP_SSH; Write-Host "Stream App Service logs at:`n" $WEB_APP_LOG_STREAM + interactive: true + continueOnError: true \ No newline at end of file diff --git a/azureproject/production.py b/azureproject/production.py index 1a95b3ec..eb530a6f 100644 --- a/azureproject/production.py +++ b/azureproject/production.py @@ -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') @@ -38,3 +39,14 @@ 'PASSWORD': conn_str_params['password'], } } + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": os.environ.get('AZURE_REDIS_CONNECTIONSTRING'), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor", + }, + } +} \ No newline at end of file diff --git a/azureproject/settings.py b/azureproject/settings.py index 83e6d8d0..5d890707 100644 --- a/azureproject/settings.py +++ b/azureproject/settings.py @@ -53,6 +53,7 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +SESSION_ENGINE = "django.contrib.sessions.backends.cache" ROOT_URLCONF = 'azureproject.urls' TEMPLATES = [ @@ -119,6 +120,15 @@ }, ] +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": os.environ.get('CACHELOCATION'), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + }, + } +} # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ diff --git a/infra/README.md b/infra/README.md new file mode 100644 index 00000000..b6a6c2e0 --- /dev/null +++ b/infra/README.md @@ -0,0 +1,3 @@ +# infra directory + +This `infra` directory contains azd files used for `azd provision` and isn't used by the sample application. diff --git a/infra/main.bicep b/infra/main.bicep index c67462e9..52b60ff3 100644 --- a/infra/main.bicep +++ b/infra/main.bicep @@ -42,3 +42,7 @@ module resources 'resources.bicep' = { output AZURE_LOCATION string = location output APPLICATIONINSIGHTS_CONNECTION_STRING string = resources.outputs.APPLICATIONINSIGHTS_CONNECTION_STRING output WEB_URI string = resources.outputs.WEB_URI +output WEB_APP_SETTINGS array = resources.outputs.WEB_APP_SETTINGS +output WEB_APP_LOG_STREAM string = resources.outputs.WEB_APP_LOG_STREAM +output WEB_APP_SSH string = resources.outputs.WEB_APP_SSH +output WEB_APP_CONFIG string = resources.outputs.WEB_APP_CONFIG \ No newline at end of file diff --git a/infra/resources.bicep b/infra/resources.bicep index 3e3577ff..4fa1debb 100644 --- a/infra/resources.bicep +++ b/infra/resources.bicep @@ -13,6 +13,12 @@ var pgServerName = '${prefix}-postgres-server' var databaseSubnetName = 'database-subnet' var webappSubnetName = 'webapp-subnet' +// Added for Azure Redis Cache +var cacheServerName = '${prefix}-redisCache' +var cacheSubnetName = 'cache-subnet' +var cachePrivateEndpointName = 'cache-privateEndpoint' +var cachePvtEndpointDnsGroupName = 'cacheDnsGroup' + resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = { name: '${prefix}-vnet' location: location @@ -52,6 +58,12 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = { ] } } + { + name: cacheSubnetName + properties:{ + addressPrefix: '10.0.2.0/24' + } + } ] } resource databaseSubnet 'subnets' existing = { @@ -60,6 +72,10 @@ resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = { resource webappSubnet 'subnets' existing = { name: webappSubnetName } + // Added for Azure Redis Cache + resource cacheSubnet 'subnets' existing = { + name: cacheSubnetName + } } resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { @@ -71,6 +87,16 @@ resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = { ] } +// Added for Azure 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' @@ -83,6 +109,54 @@ resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLin } } +// Added for Azure 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 @@ -92,7 +166,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' } @@ -105,9 +179,12 @@ resource web 'Microsoft.Web/sites@2022-03-01' = { resource appSettings 'config' = { name: 'appsettings' properties: { - 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' + AZURE_POSTGRESQL_CONNECTIONSTRING: 'dbname=${pythonAppDatabase.name} host=${postgresServer.name}.postgres.database.azure.com port=5432 sslmode=require user=${postgresServer.properties.administratorLogin} password=${databasePassword}' SECRET_KEY: secretKey + FLASK_DEBUG: 'False' + //Added for Azure Redis Cache + AZURE_REDIS_CONNECTIONSTRING: 'rediss://:${redisCache.listKeys().primaryKey}@${redisCache.name}.redis.cache.windows.net:6380/0' } } @@ -142,10 +219,50 @@ resource web 'Microsoft.Web/sites@2022-03-01' = { } } - dependsOn: [virtualNetwork] + dependsOn: [ virtualNetwork ] } +resource webdiagnostics 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = { + name: 'AllLogs' + scope: web + properties: { + workspaceId: logAnalyticsWorkspace.id + logs: [ + { + category: 'AppServiceHTTPLogs' + enabled: true + } + { + category: 'AppServiceConsoleLogs' + enabled: true + } + { + category: 'AppServiceAppLogs' + enabled: true + } + { + category: 'AppServiceAuditLogs' + enabled: true + } + { + category: 'AppServiceIPSecAuditLogs' + enabled: true + } + { + category: 'AppServicePlatformLogs' + enabled: true + } + ] + metrics: [ + { + category: 'AllMetrics' + enabled: true + } + ] + } +} + resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = { name: '${prefix}-service-plan' location: location @@ -183,7 +300,6 @@ module applicationInsightsResources 'appinsights.bicep' = { } } - resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' = { location: location tags: tags @@ -194,7 +310,7 @@ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-pr } properties: { version: '12' - administratorLogin: 'django' + administratorLogin: 'postgresadmin' administratorLoginPassword: databasePassword storage: { storageSizeGB: 128 @@ -223,11 +339,37 @@ resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-pr ] } - -resource djangoDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2022-01-20-preview' = { +resource pythonAppDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2022-01-20-preview' = { parent: postgresServer - name: 'django' + name: 'pythonapp' } +//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 + +resource webAppSettings 'Microsoft.Web/sites/config@2022-03-01' existing = { + name: web::appSettings.name + parent: web +} + +var webAppSettingsKeys = map(items(webAppSettings.list().properties), setting => setting.key) +output WEB_APP_SETTINGS array = webAppSettingsKeys +output WEB_APP_LOG_STREAM string = format('https://portal.azure.com/#@/resource{0}/logStream', web.id) +output WEB_APP_SSH string = format('https://{0}.scm.azurewebsites.net/webssh/host', web.name) +output WEB_APP_CONFIG string = format('https://portal.azure.com/#@/resource{0}/configuration', web.id) diff --git a/requirements.txt b/requirements.txt index d7135b9b..62cd1667 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ Django==4.2.7 psycopg2-binary==2.9.9 python-dotenv==1.0.0 -whitenoise==6.6.0 \ No newline at end of file +whitenoise==6.6.0 +django-redis==5.3.0 diff --git a/restaurant_review/templates/restaurant_review/index.html b/restaurant_review/templates/restaurant_review/index.html index 2abf02b2..1b18a859 100644 --- a/restaurant_review/templates/restaurant_review/index.html +++ b/restaurant_review/templates/restaurant_review/index.html @@ -42,6 +42,9 @@ {% endblock %} {% block content %} + {% if LastViewedRestaurant %} +

Last viewed restaurant (saved in cache): {{ LastViewedRestaurant }}

+ {% endif %}

Restaurants

{% if restaurants %} diff --git a/restaurant_review/views.py b/restaurant_review/views.py index bf02215e..42f5ee77 100644 --- a/restaurant_review/views.py +++ b/restaurant_review/views.py @@ -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 @@ -12,12 +13,14 @@ 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) + 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}) diff --git a/startup.sh b/startup.sh index 202b8add..9e843797 100644 --- a/startup.sh +++ b/startup.sh @@ -1,3 +1,4 @@ +# startup.sh is used by infra/resources.bicep to automate database migrations and isn't used by the sample application python manage.py migrate gunicorn --workers 2 --threads 4 --timeout 60 --access-logfile \ '-' --error-logfile '-' --bind=0.0.0.0:8000 \