[#41] Allow users to download the user/planng guide for a payment
- backend changes to support physical and digital checkouts
This commit is contained in:
parent
d09e6f3914
commit
13c8436d98
11 changed files with 255 additions and 179 deletions
|
@ -36,12 +36,12 @@ class ActivationKeySetAdmin(admin.ModelAdmin):
|
|||
|
||||
|
||||
class ActivationKeyAdmin(admin.ModelAdmin):
|
||||
list_display = ['key', 'creation_date', 'key_set', 'remaining_activations']
|
||||
list_display = ['key', 'creation_date', 'key_set', 'activations', 'remaining_activations']
|
||||
list_filter = ['creation_date', 'key_set']
|
||||
|
||||
|
||||
class QuestionnaireAdmin(admin.ModelAdmin):
|
||||
list_display = ['address_display', 'location_display', 'soil_variant', 'ecological_district_display', 'habitat', 'zone', 'key_set_display']
|
||||
list_display = ['address_display', 'location_display', 'soil_variant', 'ecological_district_display', 'habitat', 'zone', 'key_set_display', 'creation_date']
|
||||
list_filter = [ActivationKeySetFilter]
|
||||
actions = ['export']
|
||||
|
||||
|
@ -134,3 +134,5 @@ admin.site.register(models.ActivationKey, ActivationKeyAdmin)
|
|||
admin.site.register(models.ActivationKeySet, ActivationKeySetAdmin)
|
||||
admin.site.register(models.Questionnaire, QuestionnaireAdmin)
|
||||
admin.site.register(models.Export, ExportAdmin)
|
||||
admin.site.register(models.Customer)
|
||||
admin.site.register(models.CustomerAddress)
|
||||
|
|
|
@ -1,64 +1,7 @@
|
|||
import json
|
||||
|
||||
from django.http import Http404
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import Plant, EcologicalRegion, EcologicalDistrictLayer, ChristchurchRegion, SoilOrder, SoilVariant
|
||||
from .models import Plant, EcologicalRegion, EcologicalDistrictLayer, ChristchurchRegion, SoilOrder, SoilVariant, ActivationKey, Questionnaire
|
||||
from .wms_utils import get_point_from_coordinates
|
||||
|
||||
|
||||
def coordinate_filter(request, queryset, ignore_soil_order=False):
|
||||
coordinates = request.query_params.get('coordinates')
|
||||
|
||||
if coordinates is not None:
|
||||
pnt = get_point_from_coordinates(coordinates)
|
||||
filtered_regions = EcologicalRegion.objects.filter(
|
||||
ecologicaldistrictlayer__geom__intersects=pnt).values_list('id', flat=True)
|
||||
filtered_soil_orders = SoilOrder.objects.filter(
|
||||
soillayer__geom__intersects=pnt).values_list('id', flat=True)
|
||||
|
||||
# Filter by ecological regions and soil orders
|
||||
if ignore_soil_order:
|
||||
return queryset.filter(ecological_regions__in=filtered_regions).distinct()
|
||||
else:
|
||||
return queryset.filter(
|
||||
Q(ecological_regions__in=filtered_regions) &
|
||||
Q(soil_order__in=filtered_soil_orders)).distinct()
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
def soil_variant_filter(request, queryset):
|
||||
soil_variant = request.query_params.get('soilVariant')
|
||||
|
||||
if soil_variant in {"D", "W", "M"}:
|
||||
soil_variant_ids = SoilVariant.objects.filter(Q(name__startswith=soil_variant) | Q(
|
||||
name__startswith="M")).values_list('id', flat=True).distinct()
|
||||
return queryset.filter(soil_variants__in=soil_variant_ids).distinct()
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
def zone_filter(zone_json, queryset):
|
||||
return queryset.filter(zones__id__contains=zone_json['id']).distinct()
|
||||
|
||||
|
||||
def soil_order_coordinate_filter(coordinates):
|
||||
pnt = get_point_from_coordinates(coordinates)
|
||||
try:
|
||||
return SoilOrder.objects.filter(soillayer__geom__intersects=pnt)
|
||||
except SoilOrder.DoesNotExist:
|
||||
raise Http404(f"Soil Order cannot be found for point {pnt}")
|
||||
|
||||
|
||||
def ecological_district_coordinate_filter(coordinates):
|
||||
pnt = get_point_from_coordinates(coordinates)
|
||||
try:
|
||||
return EcologicalDistrictLayer.objects.filter(geom__intersects=pnt)
|
||||
except EcologicalDistrictLayer.DoesNotExist:
|
||||
raise Http404(
|
||||
f"Ecological district layer cannot be found for point {pnt}")
|
||||
|
||||
def is_in_auckland(coordinates):
|
||||
pnt = get_point_from_coordinates(coordinates)
|
||||
eco_district = EcologicalDistrictLayer.objects.filter(geom__intersects=pnt).first()
|
||||
|
@ -68,22 +11,31 @@ def is_in_auckland(coordinates):
|
|||
def is_in_christchurch(coordinates):
|
||||
pnt = get_point_from_coordinates(coordinates)
|
||||
in_chch = ChristchurchRegion.objects.filter(geom__intersects=pnt).first()
|
||||
return in_chch is not None;
|
||||
return in_chch is not None
|
||||
|
||||
def get_filtered_plants(request):
|
||||
filtered_plants = Plant.objects.all()
|
||||
try:
|
||||
ak = ActivationKey.objects.get(key=request.query_params['key'])
|
||||
except (KeyError, ActivationKey.DoesNotExist):
|
||||
raise ValueError("Invalid key")
|
||||
|
||||
zone = request.query_params.get('zone')
|
||||
if zone != None:
|
||||
zone_json = json.loads(zone)
|
||||
filtered_plants = zone_filter(zone_json, filtered_plants)
|
||||
if not (q := Questionnaire.objects.filter(key=ak).order_by("-creation_date").first()):
|
||||
return Plant.objects.none()
|
||||
|
||||
if not zone_json['ignore_location_filter']:
|
||||
filtered_plants = coordinate_filter(
|
||||
request, filtered_plants, ignore_soil_order=zone_json['ignore_soil_order_filter'])
|
||||
else:
|
||||
filtered_plants = coordinate_filter(request, filtered_plants)
|
||||
regions = EcologicalRegion.objects.filter(ecologicaldistrictlayer__geom__intersects=q.location).distinct()
|
||||
soils = (
|
||||
SoilVariant.objects.filter(name="Mesic") |
|
||||
SoilVariant.objects.filter(name__istartswith=q.soil_variant)
|
||||
).distinct()
|
||||
|
||||
filtered_plants = soil_variant_filter(request, filtered_plants)
|
||||
qs = Plant.objects.filter(
|
||||
zones__in=[q.zone],
|
||||
ecological_regions__in=regions,
|
||||
soil_variants__in=soils,
|
||||
)
|
||||
|
||||
return filtered_plants
|
||||
if not q.zone.ignore_soil_order_filter:
|
||||
orders = SoilOrder.objects.filter(soillayer__geom__intersects=q.location).distinct()
|
||||
qs = qs.filter(soil_order__in=orders).distinct()
|
||||
|
||||
return qs.distinct()
|
||||
|
|
50
backend/right_tree/api/migrations/0017_auto_20230420_1457.py
Normal file
50
backend/right_tree/api/migrations/0017_auto_20230420_1457.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
# Generated by Django 3.2.17 on 2023-04-20 02:57
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0016_alter_activationkey_key_set'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomerAddress',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('city', models.CharField(max_length=100)),
|
||||
('line1', models.CharField(max_length=255)),
|
||||
('line2', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('postal_code', models.SmallIntegerField()),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activationkey',
|
||||
name='activations',
|
||||
field=models.SmallIntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='questionnaire',
|
||||
name='creation_date',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Customer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('email', models.EmailField(max_length=254)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('address', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='api.customeraddress')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='activationkey',
|
||||
name='customer',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='api.customer'),
|
||||
),
|
||||
]
|
|
@ -202,6 +202,26 @@ class Address(models.Model):
|
|||
managed = False
|
||||
|
||||
|
||||
class CustomerAddress(models.Model):
|
||||
city = models.CharField(max_length=100)
|
||||
line1 = models.CharField(max_length=255)
|
||||
line2 = models.CharField(max_length=255, blank=True, null=True)
|
||||
postal_code = models.SmallIntegerField()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.line1}, {self.line2}, {self.city} {self.postal_code}" \
|
||||
if self.line2 else f"{self.line1}, {self.city} {self.postal_code}"
|
||||
|
||||
|
||||
class Customer(models.Model):
|
||||
email = models.EmailField()
|
||||
name = models.CharField(max_length=100)
|
||||
address = models.ForeignKey(CustomerAddress, on_delete=models.PROTECT)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class ActivationKeySet(models.Model):
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
name = models.CharField(max_length=255, unique=True)
|
||||
|
@ -223,8 +243,10 @@ class ActivationKey(models.Model):
|
|||
|
||||
key = models.CharField(max_length=20, unique=True, default=key_default)
|
||||
key_set = models.ForeignKey(ActivationKeySet, on_delete=models.PROTECT, null=True)
|
||||
activations = models.SmallIntegerField(default=0)
|
||||
remaining_activations = models.SmallIntegerField(default=1)
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
customer = models.ForeignKey(Customer, on_delete=models.PROTECT, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
@ -235,6 +257,7 @@ class Questionnaire(models.Model):
|
|||
soil_variant = models.ForeignKey(SoilVariant, on_delete=models.CASCADE)
|
||||
zone = models.ForeignKey(Zone, on_delete=models.CASCADE)
|
||||
key = models.ForeignKey(ActivationKey, on_delete=models.PROTECT, null=True)
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@property
|
||||
def habitat(self):
|
||||
|
|
|
@ -2,8 +2,9 @@ import csv
|
|||
from io import StringIO
|
||||
from os.path import splitext
|
||||
|
||||
from .filters import *
|
||||
from .wms_utils import get_address_from_coordinates, get_point_from_coordinates
|
||||
from .filters import is_in_christchurch, is_in_auckland
|
||||
from .models import EcologicalDistrictLayer, SoilOrder, Questionnaire, ActivationKey
|
||||
from .wms_utils import get_address_from_coordinates
|
||||
|
||||
import pdfkit
|
||||
import pandas as pd
|
||||
|
@ -22,74 +23,57 @@ HEADER_FIELDS = ['Names', 'Growth Form / Max Height (m) / Spacing (m) / Forest P
|
|||
'Tolerances (Water / Drought / Frost / Salinity)', 'Ecosystem Services', 'Carbon Sequestration Rate', 'Planting Stage']
|
||||
|
||||
|
||||
def get_location_filters(params):
|
||||
def get_location_filters(questionnaire):
|
||||
""" Retrives the selected location data from the request.
|
||||
"""
|
||||
filter_rows = [['LOCATION FILTERS:', ' ']]
|
||||
coordinates = params.get('coordinates')
|
||||
|
||||
if coordinates is not None:
|
||||
eco_district_layer = ecological_district_coordinate_filter(
|
||||
coordinates).first()
|
||||
point = get_point_from_coordinates(coordinates)
|
||||
address = get_address_from_coordinates(coordinates)
|
||||
eco_district_layer = EcologicalDistrictLayer.objects.filter(geom__intersects=questionnaire.location).first()
|
||||
address = get_address_from_coordinates(questionnaire.location)
|
||||
|
||||
filter_rows.append(['Point coordinates:', point])
|
||||
filter_rows.append(['Point coordinates:', questionnaire.location])
|
||||
filter_rows.append(['Ecological region:', eco_district_layer.ecologic_1 or '' ])
|
||||
filter_rows.append(['Ecological district:', eco_district_layer.ecologic_2 or ' '])
|
||||
filter_rows.append(['Property address:', address['full_address'] if address is not None else ' '])
|
||||
else:
|
||||
filter_rows.append(["None specified", " "])
|
||||
|
||||
return filter_rows
|
||||
|
||||
|
||||
def get_soil_filters(params):
|
||||
def get_soil_filters(questionnaire):
|
||||
""" Retrives the selected soil type data from the request params.
|
||||
"""
|
||||
filter_rows = [['SOIL FILTERS:', ' ']]
|
||||
soil_variant = params.get('soilVariant')
|
||||
coordinates = params.get('coordinates')
|
||||
|
||||
if soil_variant is not None and coordinates is not None:
|
||||
soil_order_obj = soil_order_coordinate_filter(coordinates).first()
|
||||
soil_order = SoilOrder.objects.filter(soillayer__geom__intersects=questionnaire.location).first()
|
||||
|
||||
filter_rows.append(['Soil Order:', f"{soil_order_obj.name or ' '} ({soil_order_obj.code or ' '})"])
|
||||
filter_rows.append(['Soil Variant:', soil_variant])
|
||||
else:
|
||||
filter_rows.append(["None specified", " "])
|
||||
filter_rows.append(['Soil Order:', f"{soil_order.name or ' '} ({soil_order.code or ' '})"])
|
||||
filter_rows.append(['Soil Variant:', questionnaire.soil_variant])
|
||||
|
||||
return filter_rows
|
||||
|
||||
|
||||
def get_site_filters(params):
|
||||
def get_site_filters(questionnaire):
|
||||
""" Retrives the selected site data from the request params
|
||||
"""
|
||||
filter_rows = [['SITE FILTERS:', ' ']]
|
||||
|
||||
habitat = params.get('habitat')
|
||||
zone = params.get('zone')
|
||||
if zone is not None and habitat is not None:
|
||||
habitat_json = json.loads(habitat)
|
||||
zone_json = json.loads(zone)
|
||||
filter_rows.append(['Habitat:', habitat_json.get("name", " ")])
|
||||
filter_rows.append(['Zone Name:', zone_json.get("name", " ")])
|
||||
filter_rows.append(['Zone Variant:', zone_json.get("variant", " ")])
|
||||
filter_rows.append(['Zone Refined Variant:', zone_json.get("refined_variant", " ")])
|
||||
else:
|
||||
filter_rows.append(["None specified", " "])
|
||||
zone = questionnaire.zone
|
||||
habitat = zone.habitat
|
||||
|
||||
filter_rows.append(['Habitat:', habitat.name])
|
||||
filter_rows.append(['Zone Name:', zone.name])
|
||||
filter_rows.append(['Zone Variant:', zone.variant])
|
||||
filter_rows.append(['Zone Refined Variant:', zone.refined_variant])
|
||||
|
||||
return filter_rows
|
||||
|
||||
|
||||
def get_additional_region_info(params):
|
||||
def get_additional_region_info(questionnaire):
|
||||
""" If the location coordinates fall within the CHCH or Auckland regions then return a description of where to find more information.
|
||||
"""
|
||||
coordinates = params.get('coordinates')
|
||||
if coordinates is not None:
|
||||
if is_in_christchurch(coordinates):
|
||||
if is_in_christchurch(questionnaire.location):
|
||||
return [["Your location falls within the ecosystem type covered by the Christchurch Council ecosystem maps - further information can be obtained from Ōtautahi/Christchurch ecosystems map link to: https://ccc.govt.nz/environment/land/ecosystem-map", " "], [' ', ' ']]
|
||||
elif is_in_auckland(coordinates):
|
||||
elif is_in_auckland(questionnaire.location):
|
||||
return [["Your location falls within the ecosystem type covered by the Auckland Council Tiaki Tāmaki Makaurau Conservation map - further information can be obtained from tiaki Tāmaki Makaurau conservation Auckland - link to https://www.tiakitamakimakaurau.nz/conservation-map/", " "], [' ', ' ']]
|
||||
|
||||
return []
|
||||
|
@ -99,19 +83,25 @@ def get_filter_values(params):
|
|||
"""
|
||||
filter_rows = []
|
||||
|
||||
try:
|
||||
ak = ActivationKey.objects.get(key=params.get("key", ""))
|
||||
q = Questionnaire.objects.get(key=ak)
|
||||
except (ActivationKey.DoesNotExist, Questionnaire.DoesNotExist):
|
||||
return filter_rows
|
||||
|
||||
# Add all the location filters
|
||||
filter_rows += get_location_filters(params)
|
||||
filter_rows += get_location_filters(q)
|
||||
filter_rows.append([' ', ' '])
|
||||
|
||||
# Add the soil filters
|
||||
filter_rows += get_soil_filters(params)
|
||||
filter_rows += get_soil_filters(q)
|
||||
filter_rows.append([' ', ' '])
|
||||
|
||||
# Add the project site filters
|
||||
filter_rows += get_site_filters(params)
|
||||
filter_rows += get_site_filters(q)
|
||||
filter_rows.append([' ', ' '])
|
||||
|
||||
filter_rows += get_additional_region_info(params)
|
||||
filter_rows += get_additional_region_info(q)
|
||||
|
||||
return filter_rows
|
||||
|
||||
|
|
|
@ -104,7 +104,7 @@ class QuestionnaireSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Questionnaire
|
||||
fields = '__all__'
|
||||
exclude = ['id']
|
||||
|
||||
def validate_soil_variant(self, value):
|
||||
try:
|
||||
|
|
|
@ -32,4 +32,5 @@ def activate_key(sender, instance, created, *args, **kwargs):
|
|||
"""Consume one activation on the key associated with the created Questionnaire"""
|
||||
if created and (key := instance.key):
|
||||
key.remaining_activations -= 1
|
||||
key.activations += 1
|
||||
key.save()
|
||||
|
|
|
@ -4,20 +4,20 @@ import stripe
|
|||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseNotFound, HttpResponseBadRequest, FileResponse
|
||||
from django.contrib.gis.geos import Point
|
||||
from django.http import JsonResponse, HttpResponseNotAllowed, HttpResponseNotFound, HttpResponseBadRequest, FileResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.text import slugify
|
||||
from rest_framework import viewsets, permissions
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .models import Habitat, HabitatImage, Plant, EcologicalDistrictLayer, SoilOrder, Zone, Questionnaire, ActivationKey, ActivationKeySet
|
||||
from .models import Habitat, HabitatImage, Plant, EcologicalDistrictLayer, SoilOrder, Zone, Questionnaire, ActivationKey, ActivationKeySet, Customer, CustomerAddress
|
||||
from .serializers import HabitatImageSerializer, HabitatSerializer, PlantSerializer, SoilOrderSerializer, EcologicalDistrictLayerSerializer, AddressSerializer, ZoneSerializer, QuestionnaireSerializer
|
||||
from .filters import *
|
||||
from .wms_utils import get_address_from_coordinates, search_address
|
||||
from .resource_generation_utils import generate_csv, get_filter_values, serialize_plants_queryset, create_planting_guide_pdf, PLANTING_GUIDE_PDF_FILENAME, CSV_FILENAME, storage
|
||||
from .resource_generation_utils import generate_csv, get_filter_values, serialize_plants_queryset, create_planting_guide_pdf, PLANTING_GUIDE_PDF_FILENAME, storage
|
||||
from .redis import redis_client
|
||||
|
||||
|
||||
|
@ -42,12 +42,15 @@ class SoilOrderViewSet(viewsets.ModelViewSet):
|
|||
def get_queryset(self):
|
||||
""" Filtering soil order query set by coordinate parameters in the URL.
|
||||
"""
|
||||
coordinates = self.request.query_params.get('coordinates')
|
||||
if coordinates is not None:
|
||||
return soil_order_coordinate_filter(coordinates)
|
||||
|
||||
try:
|
||||
lat = float(self.request.query_params["lat"])
|
||||
lng = float(self.request.query_params["lng"])
|
||||
except (KeyError, ValueError):
|
||||
return SoilOrder.objects.all()
|
||||
|
||||
return SoilOrder.objects.filter(soillayer__geom__intersects=Point(lng, lat, srid=4326))
|
||||
|
||||
|
||||
|
||||
class EcologicalDistrictViewSet(viewsets.ModelViewSet):
|
||||
""" Filtered viewset for ecological district/region details.
|
||||
|
@ -57,44 +60,62 @@ class EcologicalDistrictViewSet(viewsets.ModelViewSet):
|
|||
def get_queryset(self):
|
||||
""" Filtering ecological district/region query set by coordinate parameters in the URL.
|
||||
"""
|
||||
coordinates = self.request.query_params.get('coordinates')
|
||||
if coordinates is not None:
|
||||
return ecological_district_coordinate_filter(coordinates)
|
||||
|
||||
try:
|
||||
lat = float(self.request.query_params["lat"])
|
||||
lng = float(self.request.query_params["lng"])
|
||||
except (KeyError, ValueError):
|
||||
return EcologicalDistrictLayer.objects.all()
|
||||
|
||||
return EcologicalDistrictLayer.objects.filter(geom__intersects=Point(lng, lat, srid=4326))
|
||||
|
||||
|
||||
class LINZPropertyViewSet(viewsets.ViewSet):
|
||||
""" Filtered viewset for ecological district/region details.
|
||||
"""
|
||||
|
||||
def list(self, request):
|
||||
coordinates = self.request.query_params.get('coordinates')
|
||||
try:
|
||||
lat = float(self.request.query_params["lat"])
|
||||
lng = float(self.request.query_params["lng"])
|
||||
except (KeyError, ValueError):
|
||||
lat = lng = None
|
||||
|
||||
address = self.request.query_params.get('search')
|
||||
|
||||
if address is not None:
|
||||
results = search_address(address)
|
||||
return Response(results)
|
||||
elif coordinates is not None:
|
||||
address_data = get_address_from_coordinates(coordinates)
|
||||
elif lat and lng:
|
||||
address_data = get_address_from_coordinates(Point(lng, lat, srid=4326))
|
||||
serializer = AddressSerializer(address_data)
|
||||
return Response(serializer.data)
|
||||
else:
|
||||
return HttpResponseBadRequest("No parameters given.")
|
||||
|
||||
return HttpResponseBadRequest("Invalid parameters.")
|
||||
|
||||
|
||||
class AuckCHCHRegionInformation(viewsets.ViewSet):
|
||||
""" Filtered viewset defining if coordinate falls inside auckland and chch regions.
|
||||
"""
|
||||
|
||||
def list(self, request):
|
||||
coordinates = self.request.query_params.get('coordinates')
|
||||
if coordinates is not None:
|
||||
in_chch = is_in_christchurch(coordinates)
|
||||
in_auckland = is_in_auckland(coordinates)
|
||||
region_details = {"in_chch": in_chch, "in_auckland": in_auckland}
|
||||
return Response(region_details)
|
||||
else:
|
||||
return HttpResponseBadRequest("No coordinate given.")
|
||||
try:
|
||||
lat = float(self.request.query_params["lat"])
|
||||
lng = float(self.request.query_params["lng"])
|
||||
except (KeyError, ValueError):
|
||||
return HttpResponseBadRequest("Missing or invalid coordinates.")
|
||||
|
||||
p = Point(lng, lat, srid=4326)
|
||||
in_chch = False
|
||||
in_auckland = False
|
||||
|
||||
# can avoid computing intersections for Auckland if we use a conditional here
|
||||
if is_in_christchurch(p):
|
||||
in_chch = True
|
||||
elif is_in_auckland(p):
|
||||
in_auckland = True
|
||||
|
||||
return Response({"in_chch": in_chch, "in_auckland": in_auckland})
|
||||
|
||||
|
||||
class HabitatViewSet(viewsets.ModelViewSet):
|
||||
""" Viewset for all habitats.
|
||||
|
@ -188,35 +209,62 @@ def validate_key(request):
|
|||
return HttpResponseBadRequest("'key' not specified")
|
||||
|
||||
try:
|
||||
if ActivationKey.objects.get(key=key).remaining_activations > 0:
|
||||
return HttpResponse()
|
||||
ak = ActivationKey.objects.get(key=key)
|
||||
except ActivationKey.DoesNotExist:
|
||||
pass
|
||||
|
||||
return HttpResponseNotFound()
|
||||
|
||||
if ak.remaining_activations > 0:
|
||||
# valid key, permit entry
|
||||
return JsonResponse({"type": ak.key_set.name})
|
||||
elif ak.activations == 1:
|
||||
# key has been activated, but can return the existing data from that activation
|
||||
return JsonResponse(QuestionnaireSerializer(ak.questionnaire_set.first()).data)
|
||||
|
||||
# key has multiple activations, but all are expended
|
||||
# could return most recent questionnaire but user who uses many-use keys probably doesn't care
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
def activate_key(request):
|
||||
"""Adds a single activation to a given key if a Stripe payment has succeeded"""
|
||||
redirect_url = "/apply"
|
||||
|
||||
try:
|
||||
stripe.api_key = settings.STRIPE_API_KEY
|
||||
|
||||
redirect_url = "/"
|
||||
|
||||
key = request.GET['key']
|
||||
stripe_session_id = redis_client.getdel(key).decode()
|
||||
stripe_session_id = redis_client.get(key).decode()
|
||||
stripe_session = stripe.checkout.Session.retrieve(stripe_session_id)
|
||||
status = stripe_session.payment_status
|
||||
except (KeyError, AttributeError):
|
||||
return redirect(redirect_url)
|
||||
is_physical = stripe_session.metadata.get("physical") == "true"
|
||||
kwargs = {}
|
||||
|
||||
match status:
|
||||
case "paid":
|
||||
ActivationKey.objects.create(
|
||||
key=key,
|
||||
key_set=ActivationKeySet.objects.get_or_create(name="Stripe", size=0)[0],
|
||||
if is_physical:
|
||||
address, _ = CustomerAddress.objects.get_or_create(
|
||||
city=stripe_session.customer_details.address['city'],
|
||||
line1=stripe_session.customer_details.address['line1'],
|
||||
line2=stripe_session.customer_details.address['line2'],
|
||||
postal_code=int(stripe_session.customer_details.address['postal_code']),
|
||||
)
|
||||
customer, _ = Customer.objects.get_or_create(
|
||||
email=stripe_session.customer_details.email,
|
||||
name=stripe_session.customer_details.name,
|
||||
address=address,
|
||||
)
|
||||
key_set, _ = ActivationKeySet.objects.get_or_create(
|
||||
name=settings.STRIPE_PHYSICAL_KEY_SET,
|
||||
size=0,
|
||||
)
|
||||
kwargs["customer"] = customer
|
||||
else:
|
||||
key_set, _ = ActivationKeySet.objects.get_or_create(
|
||||
name=settings.STRIPE_DIGITAL_KEY_SET,
|
||||
size=0,
|
||||
)
|
||||
|
||||
if stripe_session.payment_status == "paid":
|
||||
ActivationKey.objects.create(key=key, key_set=key_set, **kwargs)
|
||||
redis_client.delete(key)
|
||||
redirect_url += "?key=" + key
|
||||
case "open":
|
||||
stripe.checkout.Session.expire(stripe_session_id)
|
||||
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
@ -225,13 +273,28 @@ def purchase_key(request):
|
|||
"""Generate a prospective key and redirect to the Stripe payment portal"""
|
||||
|
||||
stripe.api_key = settings.STRIPE_API_KEY
|
||||
price_id = settings.STRIPE_DIGITAL_PRICE_ID
|
||||
extra_kwargs = {}
|
||||
|
||||
key = ActivationKey.key_default()
|
||||
redirect_url = request.build_absolute_uri(reverse(activate_key)) + f"?key={key}"
|
||||
|
||||
# requesting checkout for physical copy
|
||||
if request.GET.get("physical", "").lower() in {"t", "true", "y", "yes", "1"}:
|
||||
price_id = settings.STRIPE_PHYSICAL_PRICE_ID
|
||||
extra_kwargs = {
|
||||
'shipping_address_collection': {
|
||||
'allowed_countries': ['NZ'],
|
||||
},
|
||||
'metadata': {
|
||||
'physical': 'true',
|
||||
},
|
||||
}
|
||||
|
||||
stripe_session = stripe.checkout.Session.create(
|
||||
line_items=[
|
||||
{
|
||||
"price": settings.STRIPE_PRICE_ID,
|
||||
"price": price_id,
|
||||
"quantity": 1,
|
||||
},
|
||||
],
|
||||
|
@ -247,6 +310,7 @@ def purchase_key(request):
|
|||
mode='payment',
|
||||
success_url=redirect_url,
|
||||
cancel_url=redirect_url,
|
||||
**extra_kwargs,
|
||||
)
|
||||
redis_client.setex(key, timedelta(hours=8), stripe_session.id)
|
||||
|
||||
|
|
|
@ -164,4 +164,7 @@ CELERY_BROKER_URL = REDIS_CELERY_URL
|
|||
|
||||
# Stripe payment processing
|
||||
STRIPE_API_KEY = os.environ['STRIPE_API_KEY']
|
||||
STRIPE_PRICE_ID = os.environ['STRIPE_PRICE_ID']
|
||||
STRIPE_DIGITAL_PRICE_ID = os.environ['STRIPE_DIGITAL_PRICE_ID']
|
||||
STRIPE_PHYSICAL_PRICE_ID = os.environ['STRIPE_PHYSICAL_PRICE_ID']
|
||||
STRIPE_DIGITAL_KEY_SET = "Stripe - digital"
|
||||
STRIPE_PHYSICAL_KEY_SET = "Stripe - physical"
|
||||
|
|
|
@ -26,6 +26,7 @@ services:
|
|||
- manage.py
|
||||
- collectstatic
|
||||
- --noinput
|
||||
restart: on-failure
|
||||
|
||||
backend:
|
||||
<<: *django
|
||||
|
|
|
@ -13,14 +13,6 @@ x-django: &django
|
|||
restart: unless-stopped
|
||||
|
||||
services:
|
||||
collectstatic:
|
||||
<<: *django
|
||||
container_name: collectstatic
|
||||
command:
|
||||
- python
|
||||
- manage.py
|
||||
- collectstatic
|
||||
- --noinput
|
||||
|
||||
backend:
|
||||
<<: *django
|
||||
|
@ -32,8 +24,6 @@ services:
|
|||
condition: service_healthy
|
||||
celery:
|
||||
condition: service_healthy
|
||||
collectstatic:
|
||||
condition: service_completed_successfully
|
||||
expose:
|
||||
- "8000"
|
||||
command:
|
||||
|
|
Loading…
Reference in a new issue