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

Improve API (API 1.1) #421

Merged
merged 28 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
88f1114
Streamline GetMemberID with other Member-API endpoints
krestenlaust Apr 7, 2024
1b2a2e5
Consistent member/user terminology
krestenlaust Apr 8, 2024
7414038
Consistent view-method naming
krestenlaust Apr 8, 2024
50ec758
Make get ID consistent as well
krestenlaust Apr 8, 2024
1a04d40
Rename Get Payment QR-endpoint
krestenlaust Apr 8, 2024
c7141fd
Refactor Get Payment QR
krestenlaust Apr 8, 2024
00a7723
Rename named_products method
krestenlaust Apr 16, 2024
2479a6f
Change return text
krestenlaust Apr 17, 2024
aadf199
Remove use of annotations from future
krestenlaust Apr 17, 2024
2b24bc1
Object instead of array in Active Products-endpoint
krestenlaust Apr 17, 2024
65cb2f5
TODO Check whether room exists
krestenlaust Apr 17, 2024
b8679eb
Rename URL name
krestenlaust Apr 17, 2024
121f7b2
Strongly typed return-object for category mapping
krestenlaust May 4, 2024
da61929
Make QR Form and helper function generic
krestenlaust May 12, 2024
06aa0cb
Merge branch 'next' into improve-api
krestenlaust May 12, 2024
a5d2a04
Merge branch 'next' into improve-api
krestenlaust May 17, 2024
3015ca2
Merge branch 'next' into improve-api
krestenlaust Oct 24, 2024
21aaeb1
Rename 'member' -> 'username'-parameter
krestenlaust Nov 2, 2024
837b6f0
Merge branch 'next' into improve-api
krestenlaust Nov 12, 2024
cdaff44
Change minimum value for QR-code form to 0
krestenlaust Nov 27, 2024
40e878f
Fix incorrect 400 response body's
krestenlaust Nov 27, 2024
9c6a5f7
Make api/sale-endpoint responses more specific
krestenlaust Nov 27, 2024
dba5abd
Remove unused import
krestenlaust Nov 28, 2024
3c9f739
Rename view-functions
krestenlaust Nov 28, 2024
f0f5b50
Change naming scheme
krestenlaust Nov 28, 2024
3042946
Merge branch 'next' into improve-api
krestenlaust Nov 28, 2024
adbdf88
Merge branch 'next' into improve-api
krestenlaust Nov 29, 2024
88c893c
Merge branch 'next' into improve-api
krestenlaust Nov 29, 2024
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
2 changes: 1 addition & 1 deletion stregsystem/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __init__(self, *args, **kwargs):

class QRPaymentForm(forms.Form):
member = forms.CharField(max_length=16)
amount = forms.DecimalField(min_value=50, decimal_places=2, required=False)
amount = forms.DecimalField(min_value=0, decimal_places=2, required=False)


class PurchaseForm(forms.Form):
Expand Down
20 changes: 10 additions & 10 deletions stregsystem/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
re_path(r'^(?P<room_id>\d+)/user/(?P<member_id>\d+)/pay$', views.menu_userpay, name="userpay"),
re_path(r'^(?P<room_id>\d+)/user/(?P<member_id>\d+)/rank$', views.menu_userrank, name="userrank"),
re_path(r'^(?P<room_id>\d+)/send_csv_mail/(?P<member_id>\d+)/$', views.send_userdata, name="send_userdata"),
re_path(r'^api/member/payment/qr$', views.qr_payment, name="payment_qr"),
re_path(r'^api/member/active$', views.check_user_active, name="active_member"),
re_path(r'^api/member/sales$', views.get_user_sales, name="get_user_sales"),
re_path(r'^api/member/get_id$', views.convert_username_to_id, name="get_id"),
re_path(r'^api/member/balance$', views.get_user_balance, name="get_user_balance"),
re_path(r'^api/member$', views.get_user_info, name="get_user_transactions"),
re_path(r'^api/products/named_products$', views.dump_named_items, name="named_products"),
re_path(r'^api/products/active_products$', views.dump_active_items, name="active_products"),
re_path(r'^api/products/category_mappings$', views.dump_product_category_mappings, name="product_mappings"),
re_path(r'^api/sale$', views.api_sale, name="sale"),
re_path(r'^api/member/payment/qr$', views.get_payment_qr, name="api_payment_qr"),
re_path(r'^api/member/active$', views.get_member_active, name="api_member_active"),
re_path(r'^api/member/sales$', views.get_member_sales, name="api_member_sales"),
re_path(r'^api/member/get_id$', views.get_member_id, name="api_member_id"),
re_path(r'^api/member/balance$', views.get_member_balance, name="api_member_balance"),
re_path(r'^api/member$', views.get_member_info, name="api_member_info"),
re_path(r'^api/products/named_products$', views.get_named_products, name="api_named_products"),
re_path(r'^api/products/active_products$', views.get_active_items, name="api_active_products"),
re_path(r'^api/products/category_mappings$', views.get_product_category_mappings, name="api_product_mappings"),
re_path(r'^api/sale$', views.api_sale, name="api_sale"),
]
13 changes: 12 additions & 1 deletion stregsystem/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import qrcode
import qrcode.image.svg

import urllib.parse

logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -161,14 +163,23 @@ def strip_emoji(text):
).strip()


def qr_code(data):
def qr_code(data) -> HttpResponse:
response = HttpResponse(content_type="image/svg+xml")
qr = qrcode.make(data, image_factory=qrcode.image.svg.SvgPathFillImage)
qr.save(response)

return response


def mobilepay_launch_uri(comment: str, amount: float) -> str:
query = {'phone': '90601', 'comment': comment}

if amount is not None:
query['amount'] = amount

return 'mobilepay://send?{}'.format(urllib.parse.urlencode(query))


class stregsystemTestRunner(DiscoverRunner):
def __init__(self, *args, **kwargs):
settings.TEST_MODE = True
Expand Down
92 changes: 52 additions & 40 deletions stregsystem/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import datetime
import io
import json
import urllib.parse
from typing import List, Type

import pytz
Expand All @@ -20,6 +19,7 @@
from django.shortcuts import get_object_or_404, render, redirect
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django_select2 import forms as s2forms

from stregreport.views import fjule_party

Expand All @@ -44,6 +44,7 @@
from stregsystem.utils import (
make_active_productlist_query,
qr_code,
mobilepay_launch_uri,
make_room_specific_query,
make_unprocessed_mobilepayment_query,
parse_csv_and_create_mobile_payments,
Expand Down Expand Up @@ -563,18 +564,18 @@ def signup_tool(request):
return render(request, "admin/stregsystem/approval_tools/signup_tool.html", data)


def qr_payment(request):
# API views


def get_payment_qr(request):
form = QRPaymentForm(request.GET)
if not form.is_valid():
return HttpResponseBadRequest("Invalid input for MobilePay QR code generation")

query = {'phone': '90601', 'comment': form.cleaned_data.get('member')}

if form.cleaned_data.get("amount") is not None:
query['amount'] = form.cleaned_data.get("amount")
username = form.cleaned_data.get('username')
amount = form.cleaned_data.get('amount')

data = 'mobilepay://send?{}'.format(urllib.parse.urlencode(query))
krestenlaust marked this conversation as resolved.
Show resolved Hide resolved
return qr_code(data)
return qr_code(mobilepay_launch_uri(username, amount))


def signup(request):
Expand Down Expand Up @@ -620,78 +621,85 @@ def signup_status(request, signup_id):
return render(request, "stregsystem/signup_status.html", locals())


# API views


def dump_active_items(request):
def get_active_items(request):
room_id = request.GET.get('room_id') or None
if room_id is None:
return HttpResponseBadRequest("Missing room_id")
return HttpResponseBadRequest("Parameter missing: room_id")
elif not room_id.isdigit():
return HttpResponseBadRequest("Invalid room_id")
return HttpResponseBadRequest("Parameter invalid: room_id")
# TODO: Check whether room exists
items = __get_productlist(room_id)
items_dict = {item.id: (item.name, item.price) for item in items}
items_dict = {item.id: {'name': item.name, 'price': item.price} for item in items}
return JsonResponse(items_dict, json_dumps_params={'ensure_ascii': False})


def check_user_active(request):
def get_member_active(request):
member_id = request.GET.get('member_id') or None
if member_id is None:
return HttpResponseBadRequest("Missing member_id")
return HttpResponseBadRequest("Parameter missing: member_id")
elif not member_id.isdigit():
return HttpResponseBadRequest("Invalid member_id")
return HttpResponseBadRequest("Parameter invalid: member_id")
try:
member = Member.objects.get(pk=member_id)
except Member.DoesNotExist:
return HttpResponseBadRequest("Member not found")
return JsonResponse({'active': member.active})


def convert_username_to_id(request):
def get_member_id(request):
username = request.GET.get('username') or None
if username is None:
return HttpResponseBadRequest("Missing username")
return HttpResponseBadRequest("Parameter missing: username")

try:
member = Member.objects.get(username=username)
except Member.DoesNotExist:
return HttpResponseBadRequest("Invalid username")
return HttpResponseBadRequest("Member not found")
krestenlaust marked this conversation as resolved.
Show resolved Hide resolved

return JsonResponse({'member_id': member.id})


def dump_product_category_mappings(request):
return JsonResponse({p.id: [(cat.id, cat.name) for cat in p.categories.all()] for p in Product.objects.all()})
def get_product_category_mappings(request):
return JsonResponse(
{
p.id: [{'category_id': cat.id, 'category_name': cat.name} for cat in p.categories.all()]
for p in Product.objects.all()
}
)


def get_user_sales(request):
def get_member_sales(request):
member_id = request.GET.get('member_id') or None
if member_id is None:
return HttpResponseBadRequest("Missing member_id")
return HttpResponseBadRequest("Parameter missing: member_id")
elif not member_id.isdigit():
return HttpResponseBadRequest("Invalid member_id")
return HttpResponseBadRequest("Parameter invalid: member_id")
count = 10 if request.GET.get('count') is None else int(request.GET.get('count') or 10)
sales = Sale.objects.filter(member=member_id).order_by('-timestamp')[:count]
return JsonResponse(
{'sales': [{'timestamp': s.timestamp, 'product': s.product.name, 'price': s.product.price} for s in sales]}
)


def get_user_balance(request):
def get_member_balance(request):
member_id = request.GET.get('member_id') or None
if member_id is None:
return HttpResponseBadRequest("Missing member_id")
return HttpResponseBadRequest("Parameter missing: member_id")
elif not member_id.isdigit():
return HttpResponseBadRequest("Invalid member_id")
return HttpResponseBadRequest("Parameter invalid: member_id")
try:
member = Member.objects.get(pk=member_id)
except Member.DoesNotExist:
return HttpResponseBadRequest("Member not found")
return JsonResponse({'balance': member.balance})


def get_user_info(request):
def get_member_info(request):
member_id = str(request.GET.get('member_id')) or None
if member_id is None or not member_id.isdigit():
return HttpResponseBadRequest("Missing or invalid member_id")
if member_id is None:
return HttpResponseBadRequest("Parameter missing: member_id")
elif not member_id.isdigit():
return HttpResponseBadRequest("Parameter invalid: member_id")

member = find_user_from_id(int(member_id))
if member is None:
Expand All @@ -714,7 +722,7 @@ def find_user_from_id(user_id: int):
return None


def dump_named_items(request):
def get_named_products(request):
items = NamedProduct.objects.all()
items_dict = {item.name: item.product.id for item in items}
return JsonResponse(items_dict, json_dumps_params={'ensure_ascii': False})
Expand All @@ -730,12 +738,16 @@ def api_sale(request):
room = str(data['room']) or None
member_id = str(data['member_id']) or None

if room is None or not room.isdigit():
return HttpResponseBadRequest("Missing or invalid room")
if room is None:
return HttpResponseBadRequest("Parameter missing: room")
if not room.isdigit():
return HttpResponseBadRequest("Parameter invalid: room")
if buy_string is None:
return HttpResponseBadRequest("Missing buystring")
if member_id is None or not member_id.isdigit():
return HttpResponseBadRequest("Missing or invalid member_id")
return HttpResponseBadRequest("Parameter missing: buystring")
if member_id is None:
return HttpResponseBadRequest("Parameter missing: member_id")
if not member_id.isdigit():
return HttpResponseBadRequest("Parameter invalid: member_id")

try:
username, bought_ids = parser.parse(_pre_process(buy_string))
Expand All @@ -744,7 +756,7 @@ def api_sale(request):

member = find_user_from_id(int(member_id))
if member is None:
return HttpResponseBadRequest("Invalid member_id")
return HttpResponseBadRequest("Parameter invalid: member_id")

if not member.signup_due_paid:
return HttpResponseBadRequest("Signup due not paid")
Expand All @@ -761,7 +773,7 @@ def api_sale(request):
try:
room = Room.objects.get(pk=room)
except Room.DoesNotExist:
return HttpResponseBadRequest("Invalid room")
return HttpResponseBadRequest("Parameter invalid: room")
msg, status, ret_obj = api_quicksale(request, room, member, bought_ids)
return JsonResponse(
{'status': status, 'msg': msg, 'values': ret_obj}, json_dumps_params={'ensure_ascii': False}
Expand Down