🔥 Delete legacy register and deposit

♻ Replace facebook avatar related stuff with gravatars
♻ Replace format-strings with f-strings
✨ Add profile_images command
This commit is contained in:
Nikolai R Kristiansen committed 2022-02-10 09:42:57 +01:00
1 parent a9f81737d5
commit 9fa6cfe0ee
13 files changed
+52 -626

No files matched your search

+1 -43
View File
@@ -1,56 +1,14 @@
from django import forms
from django.contrib.auth.models import User
from main.models import InventoryTransaction, Order, Product, Transaction, UserProfile
from main.models import InventoryTransaction, Product, UserProfile
from main.utils import format_username
class OrderForm(forms.ModelForm):
customer_name = forms.CharField(required=False)
def clean(self):
if self.cleaned_data["customer"] is None and self.cleaned_data["customer_name"] != "Ekstern":
raise forms.ValidationError("Du har ikke valgt hvem som skal kjøpe.")
return self.cleaned_data
def clean_customer(self):
if self.cleaned_data["customer"] is None:
return self.cleaned_data["customer"]
if self.cleaned_data["customer"].profile.balance <= 0:
raise forms.ValidationError("Du har negativ saldo og kan ikke kjøpe noe.")
return self.cleaned_data["customer"]
class Meta:
model = Order
fields = ("customer",)
class OrderLineForm(forms.Form):
product = forms.IntegerField(required=True)
amount = forms.IntegerField(required=True)
unit_price_int = forms.FloatField(required=True, localize=True)
unit_price_ext = forms.FloatField(required=True, localize=True)
class DepositForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ["user", "amount"]
class SimpleCreateUserForm(forms.Form):
first_name = forms.CharField(required=True, label="Fornavn")
last_name = forms.CharField(required=True, label="Etternavn")
email = forms.EmailField(required=True, label="E-post")
facebook_username = forms.CharField(required=False, label="Facebook-brukernavn")
def _get_facebook_url(self):
if self.cleaned_data["facebook_username"] == "":
return ""
return "https://graph.facebook.com/{0}/picture".format(self.cleaned_data["facebook_username"])
def save(self, commit=True):
username = format_username(self.cleaned_data["first_name"], self.cleaned_data["last_name"])
-46
View File
@@ -1,46 +0,0 @@
import difflib
import facebook
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
from main.models import UserProfile
class Command(BaseCommand):
"""
- Fetches members of KAK Facebook group
- Matches names against the local DB
- Saves the users Facebook picture in his/her user profile.
"""
KAK_GROUP_ID = "108265352570560"
graph = None
def get_kak_members(self):
members = self.graph.get_connections(id=self.KAK_GROUP_ID, connection_name="members")
return [(member["name"], member["id"]) for member in members["data"]]
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("Missing access token")
access_token = args[0]
self.graph = facebook.GraphAPI(access_token=access_token)
group_members = self.get_kak_members()
names = [] # to fuzzy search for a name
name_lookup = {} # to update the matching UserProfile object
for u in User.objects.all().order_by("id"):
fullname = "{} {}".format(u.first_name, u.last_name)
names.append(fullname)
name_lookup[fullname] = u.id
for member in group_members:
match = difflib.get_close_matches(member[0], names, 1) # max 1 match
if match:
u = UserProfile.objects.get(pk=name_lookup[match[0]])
if str(u.image) is "":
u.image = "https://graph.facebook.com/{0}/picture".format(member[1])
u.save()
@@ -0,0 +1,20 @@
from django.core.management.base import BaseCommand, CommandParser
from main.models import UserProfile
class Command(BaseCommand):
"""Set profile image URL for all users"""
def add_arguments(self, parser: CommandParser):
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--force", action="store_true")
def handle(self, *args, dry_run=False, force=False, **options):
for profile in UserProfile.objects.all().order_by("id"):
if dry_run:
print(profile.user.email, profile.image_url())
else:
if force or not profile.image:
profile.image = profile.image_url()
profile.save()
+17 -16
View File
@@ -1,3 +1,4 @@
import hashlib
from datetime import datetime
from typing import Type
@@ -108,10 +109,10 @@ class Order(models.Model):
return self.customer is None
def reciept(self):
return "{}".format(", ".join([str(ol) for ol in self.orderlines.all()]))
return ", ".join([str(ol) for ol in self.orderlines.all()])
def __str__(self):
return "{}: {} kr".format(self.customer or EXTERNAL_USER, self.order_sum)
return f"{self.customer or EXTERNAL_USER}: {self.order_sum} kr"
class OrderLine(models.Model):
@@ -125,7 +126,7 @@ class OrderLine(models.Model):
return self.amount * self.unit_price
def __str__(self):
return "{} {} ({} kr per)".format(self.amount, self.product, self.unit_price)
return f"{self.amount} {self.product} ({self.unit_price} kr per)"
class Meta:
unique_together = ("order", "product")
@@ -139,7 +140,7 @@ class Transaction(models.Model):
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "{}: {}: {}".format(self.id, self.user, self.amount)
return f"{self.id}: { self.user}: {self.amount}"
class InventoryTransaction(models.Model):
@@ -166,9 +167,7 @@ class InventoryTransaction(models.Model):
return self.amount * self.unit_price
def __str__(self):
return "{}: {}: {}. {:.0f} stykker ({} kr per)".format(
self.id, self.user, self.product, self.amount, self.unit_price
)
return f"{self.id}: {self.user}: {self.product}. {self.amount:.0f} stykker ({self.unit_price} kr per)"
class UserProfile(models.Model):
@@ -177,23 +176,25 @@ class UserProfile(models.Model):
image = models.URLField(blank=True)
def last_purchase(self):
if len(self.user.orders.all()) == 0:
return None
return self.user.orders.order_by("-created")[0]
return self.user.orders.order_by("-created").first()
def last_purchase_date(self):
last = self.last_purchase()
last_p = last.created if last else datetime.min
return last_p
def profile_image_url(self):
from django.templatetags.static import static
def image_url(self) -> str:
if self.user.email:
md5_hash = hashlib.md5(self.user.email.lower().strip().encode()).hexdigest()
return f"https://www.gravatar.com/avatar/{md5_hash}?d=identicon"
return ""
def save(self, **kwargs):
if not self.image:
return static("dist/images/unknown_person.png")
self.image = self.image_url()
return self.image
super().save(**kwargs)
def __str__(self):
return "{}".format(self.user)
return f"{self.user}"
+1 -28
View File
@@ -1,23 +1,10 @@
from django.contrib.staticfiles.views import serve
from django.urls import include, path, re_path
from django.urls import re_path
from main.views.general import deposit, register
from main.views.reports import inventory, inventory_add, report
from main.views.stats import stats_products_per_user, stats_products_realtime
from main.views.users import create_user, profile
spa_kwargs = {"path": "modern/index.html"}
legacy_patterns = [
path("", register, name="home-legacy"),
path("deposit", deposit, name="deposit-legacy"),
]
urlpatterns = [
path("", serve, kwargs=spa_kwargs, name="home"),
path("deposit", serve, kwargs=spa_kwargs, name="deposit"),
path("log", serve, kwargs=spa_kwargs, name="log"),
path("stats", serve, kwargs=spa_kwargs, name="stats"),
# TODO: migrate to SPA
# Users
re_path(r"^user/create/$", create_user, name="create-user"),
@@ -26,21 +13,7 @@ urlpatterns = [
re_path(r"^inventory/$", inventory, name="inventory"),
re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
re_path(r"^report$", report, name="report"),
path("legacy/", include(legacy_patterns)),
]
# Stats
urlpatterns += [
re_path(
r"^stats/orders/products_realtime$",
stats_products_realtime,
name="stats-products-realtime",
),
re_path(
r"^stats/products/by_user/(?P<user_id>[0-9]+)/$",
stats_products_per_user,
name="stats-products-per-user",
),
]
# Root level service-worker.js
urlpatterns += [re_path(r"^service-worker\.js$", serve, kwargs={"path": "dist/service-worker.js"})]
+2 -20
View File
@@ -1,23 +1,5 @@
from django.contrib.auth.models import User
from django.db.models import Q
def format_username(firstname, lastname):
def format_username(firstname: str, lastname: str):
first = firstname.lower().strip().replace(" ", "")[:6].encode("ascii", "ignore").decode("utf-8")
last = lastname.lower().strip().replace(" ", "")[:6].encode("ascii", "ignore").decode("utf-8")
return "{0}{1}".format(first, last)
def users_with_perm(perm_name):
return User.objects.filter(
Q(is_superuser=True) | Q(user_permissions__codename=perm_name) | Q(groups__permissions__codename=perm_name)
).distinct()
def users_format_js(users):
users_js = []
for user in users:
users_js.append('"' + user.first_name + " " + user.last_name[:1] + '"')
users_js = "[{}]".format(",".join(users_js))
return users_js
return f"{first}{last}"
-160
View File
@@ -1,160 +0,0 @@
from datetime import datetime, timedelta
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.models import User
from django.db.models import Min
from django.forms import formset_factory
from django.http import HttpResponseRedirect
from django.shortcuts import redirect, render
from django.urls import reverse
from main.forms import DepositForm, OrderForm, OrderLineForm
from main.models import OrderLine, Product
from main.utils import users_format_js, users_with_perm
def register(request):
products = Product.objects.filter(active=True)
cheapest_product_price = list(products.aggregate(Min("sale_price_int")).values())[0] or 0
half_a_year_ago = datetime.now() - timedelta(days=180)
users = User.objects.filter(profile__balance__gte=cheapest_product_price)
# sort users after user's last purchase time
users = sorted(users, key=lambda u: u.profile.last_purchase_date(), reverse=True)
users_js = users_format_js(users)
# multiple orderlines (one per product)
OrderLineFormSet = formset_factory(OrderLineForm)
if request.method == "POST":
orderform = OrderForm(request.POST)
formset = OrderLineFormSet(request.POST)
if formset.is_valid() and orderform.is_valid():
order = orderform.save(commit=False)
if order.is_external():
order_sum = sum([ol["amount"] * ol["unit_price_ext"] for ol in formset.cleaned_data])
else:
order_sum = sum([ol["amount"] * ol["unit_price_int"] for ol in formset.cleaned_data])
# empty order?
if order_sum == 0:
messages.error(request, "Du har ikke valgt hva du skal kjøpe...")
return HttpResponseRedirect(reverse("register"))
if not order.is_external():
profile = order.customer.profile
# can she afford it?
if profile.balance < order_sum:
messages.error(
request,
"{0} {1} har ikke råd, mangler {2} kr.".format(
order.customer.first_name,
order.customer.last_name,
int(order_sum - profile.balance),
),
)
return HttpResponseRedirect(reverse("register"))
# substract order from balance
profile.balance -= order_sum
profile.save()
order.order_sum = order_sum
order.save()
orderlines = []
for ol in formset.cleaned_data:
if ol["amount"] > 0:
product = Product.objects.get(pk=ol["product"])
if order.is_external():
unit_price = ol["unit_price_ext"]
else:
unit_price = ol["unit_price_int"]
OrderLine.objects.create(
order=order,
product=product,
amount=ol["amount"],
unit_price=unit_price,
)
product.inventory_amount -= ol["amount"]
product.save()
orderlines.append("{0} {1}".format(ol["amount"], product))
if order.is_external():
messages.success(request, "Ekstern kjøpte {0}.".format(", ".join(orderlines)))
else:
messages.success(
request,
"{0} {1} kjøpte {2}.".format(
order.customer.first_name,
order.customer.last_name,
", ".join(orderlines),
),
)
return redirect("register")
else:
# TODO specify error(s)
messages.error(request, "Skjemaet er ikke gyldig.")
orderform = OrderForm(request.POST)
formset = OrderLineFormSet(request.POST)
else:
orderform = OrderForm()
formset = OrderLineFormSet()
return render(request, "register.html", locals())
def deposit(request):
users = User.objects.all().order_by("first_name", "last_name")
users_js = users_format_js(users)
allowed_users = users_with_perm("add_transaction")
error_message_template = "{0} {1} kan ikke sette inn {2} kr, det overskrider maks saldo ({3} kr) med {4} kr"
limit_deposists = settings.BBS_LIMIT_DEPOSITS
if request.method == "POST":
form = DepositForm(request.POST)
if form.is_valid():
amount = form.cleaned_data["amount"]
user = form.cleaned_data["user"]
if amount + user.profile.balance > settings.BBS_SALDO_MAX and amount + user.profile.balance != 1337:
messages.error(
request,
error_message_template.format(
user.first_name,
user.last_name,
amount,
settings.BBS_SALDO_MAX,
amount + user.profile.balance - settings.BBS_SALDO_MAX,
),
)
return HttpResponseRedirect(reverse("deposit"))
transaction = form.save()
profile = transaction.user.profile
profile.balance += transaction.amount
profile.save()
messages.success(
request,
"{0} {1} satte inn {2} kr. Ny saldo er {3}".format(
transaction.user.first_name,
transaction.user.last_name,
transaction.amount,
profile.balance,
),
)
return HttpResponseRedirect(reverse("deposit"))
else:
messages.error(request, "Skjemaet er ikke gyldig.")
form = DepositForm(request.POST)
else:
form = DepositForm()
return render(request, "deposit.html", locals())
-49
View File
@@ -1,49 +0,0 @@
import time
from datetime import datetime, timedelta
from django.http import JsonResponse
from main.models import OrderLine
def stats_products_realtime(request):
end_time = datetime.now()
start_time = datetime.now() - timedelta(hours=24)
order_lines = OrderLine.objects.filter(order__created__range=(start_time, end_time)).order_by("order__created")
products = {}
for order_line in order_lines:
order_time_in_milliseconds = int(time.mktime(order_line.order.created.timetuple()) * 1000)
if order_line.product not in products:
products[order_line.product] = [[order_time_in_milliseconds, order_line.amount]]
else:
prev_entry = products[order_line.product][-1]
products[order_line.product].append([order_time_in_milliseconds, prev_entry[1] + order_line.amount])
serialized_products = {}
for product, value in products.items():
serialized_products[product.name] = value
response = {"products": serialized_products}
return JsonResponse(response)
def stats_products_per_user(request, user_id=None):
if not user_id:
return JsonResponse({"error": "Missing param user_id"})
order_lines = OrderLine.objects.filter(order__customer__pk=user_id).order_by("order__created")
products = {}
for order_line in order_lines:
order_time_in_milliseconds = int(time.mktime(order_line.order.created.timetuple()) * 1000)
if order_line.product not in products:
products[order_line.product] = [[order_time_in_milliseconds, order_line.amount]]
else:
prev_entry = products[order_line.product][-1]
products[order_line.product].append([order_time_in_milliseconds, prev_entry[1] + order_line.amount])
serialized_products = []
for product, value in products.items():
serialized_products.append({"name": product.name, "data": value})
return JsonResponse(serialized_products, safe=False)
+1 -4
View File
@@ -13,10 +13,7 @@ def create_user(request):
form = SimpleCreateUserForm(data=request.POST)
if form.is_valid():
user = form.save()
messages.success(
request,
"Hurra! {} {} er lagt til".format(user.first_name, user.last_name),
)
messages.success(request, f"Hurra! {user.first_name}{user.last_name} er lagt til")
return redirect("create-user")
else: