🔥 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:
Generated
+10 -75
View File
@@ -77,17 +77,6 @@ category = "dev"
optional = false
python-versions = ">=3.6.1"
[[package]]
name = "charset-normalizer"
version = "2.0.11"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.0.3"
@@ -166,17 +155,6 @@ python-versions = ">=3.5"
django = ">=1.11,<4.0"
pillow = "*"
[[package]]
name = "facebook-sdk"
version = "3.0.0"
description = "This client library is designed to support the Facebook Graph API and the official Facebook JavaScript SDK, which is the canonical way to implement Facebook authentication."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
requests = "*"
[[package]]
name = "filelock"
version = "3.4.2"
@@ -213,7 +191,7 @@ tornado = ["tornado (>=0.2)"]
[[package]]
name = "identify"
version = "2.4.8"
version = "2.4.9"
description = "File identification library for Python"
category = "dev"
optional = false
@@ -222,14 +200,6 @@ python-versions = ">=3.7"
[package.extras]
license = ["ukkonen"]
[[package]]
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "iniconfig"
version = "1.1.1"
@@ -305,7 +275,7 @@ python-versions = ">=3.7"
[[package]]
name = "platformdirs"
version = "2.4.1"
version = "2.5.0"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
@@ -444,24 +414,6 @@ category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "requests"
version = "2.27.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "sentinel"
version = "0.3.0"
@@ -570,7 +522,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "2.0.0"
version = "2.0.1"
description = "A lil' TOML parser"
category = "dev"
optional = false
@@ -618,7 +570,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "63af070a3bfe76cd32929214c5162596f6d693e65e88370a480237ecd8885113"
content-hash = "3fa14cb7bc28f810082b1aa4604a74060ec4ea32e63864f91a005aa37ea73c8c"
[metadata.files]
asgiref = [
@@ -670,10 +622,6 @@ cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"},
{file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"},
]
click = [
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
@@ -702,11 +650,6 @@ easy-thumbnails = [
{file = "easy-thumbnails-2.7.2.tar.gz", hash = "sha256:a7dd9cf97efaf70ba5d76484a962f08ba65b31f1681bc417257743650e9e8a8a"},
{file = "easy_thumbnails-2.7.2-py3-none-any.whl", hash = "sha256:9d7ed56a9b57aba7a8d0e03dc3b8507ca51aab5e0ed0bc705c3261ff1f659192"},
]
facebook-sdk = [
{file = "facebook-sdk-3.0.0.tar.gz", hash = "sha256:f3d450ec313b62d3716fadc4e5098183760e1d2a9e0434a94b74e59ea6ea3e4d"},
{file = "facebook_sdk-3.0.0-py2-none-any.whl", hash = "sha256:c94df84b2bba1c497684c169ed28a713b57917bf56f95fba4c2a5290c16ce81e"},
{file = "facebook_sdk-3.0.0-py3-none-any.whl", hash = "sha256:8ef0a0d2c98a515a926875bb4be9c9a4630a39fc8190bff8d5265d58af1ef257"},
]
filelock = [
{file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
{file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"},
@@ -720,12 +663,8 @@ gunicorn = [
{file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
]
identify = [
{file = "identify-2.4.8-py2.py3-none-any.whl", hash = "sha256:a55bdd671b6063eb837af938c250ec00bba6e610454265133b0d2db7ae718d0f"},
{file = "identify-2.4.8.tar.gz", hash = "sha256:97e839c1779f07011b84c92af183e1883d9745d532d83412cca1ca76d3808c1c"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
{file = "identify-2.4.9-py2.py3-none-any.whl", hash = "sha256:bff7c4959d68510bc28b99d664b6a623e36c6eadc933f89a4e0a9ddff9b4fee4"},
{file = "identify-2.4.9.tar.gz", hash = "sha256:e926ae3b3dc142b6a7a9c65433eb14ccac751b724ee255f7c2ed3b5970d764fb"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@@ -796,8 +735,8 @@ pillow = [
{file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
]
platformdirs = [
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"},
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"},
{file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"},
{file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
@@ -873,10 +812,6 @@ pyyaml = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
]
sentinel = [
{file = "sentinel-0.3.0-py3-none-any.whl", hash = "sha256:bd8710dd26752039c668604f6be2aaf741b56f7811c5924a4dcdfd74359244f3"},
{file = "sentinel-0.3.0.tar.gz", hash = "sha256:f28143aa4716dbc8f6193f5682176a3c33cd26aaae05d9ecf66c186a9887cc2d"},
@@ -906,8 +841,8 @@ toml = [
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [
{file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"},
{file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"},
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
typing-extensions = [
{file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"},
-1
View File
@@ -21,7 +21,6 @@ python = "^3.9"
django-extensions = "^3.1.3"
Django = "^3.2.8"
easy-thumbnails = "2.7.2"
facebook-sdk = "3.0.0"
gunicorn = "^20.1.0"
mysqlclient = "^2.0.3"
Pillow = "^9.0.0"
-82
View File
@@ -1,82 +0,0 @@
{% extends "base.html" %}
{% block active_deposit %}active{% endblock %}
{% block content %}
<section id="deposit" class="row">
{% if messages %}
<section class="row">
<div class="col-xs-4">
{% for message in messages %}
<div {% if message.tags %} class="alert alert-{{ message.tags }}"{% endif %}>{{ message }}<a class="close" data-dismiss="alert">×</a></div>
{% endfor %}
</div>
</section>
{% endif %}
{% if not perms.main.can_add_transaction and limit_deposits %}
<section class="row">
<div class="col-xs-4">
<p class="alert alert-warning">Du har ikke tilgang til å sette inn penger.</p>
<p>Spør en av disse om de kan hjelpe deg:</p>
<ul>
{% for u in allowed_users %}
<li style="padding-bottom: 8px;"><img src="{{u.profile.image }}" />{{u.first_name }} {{u.last_name }}</li>
{% endfor %}
</ul>
</div>
</section>
{% else %}
<form id="order" method="post" class="form-stacked" autocomplete="off">
{% csrf_token %}
<section id="customer">
<div class="row">
<div class="col-md-12">
<h2>Hvem skal sette inn penger?</h2>
</div>
</div>
<div class="row">
<!-- Typeahead customer field -->
<div class="col-md-3">
<input name="customer_name" id="id_customer_typeahead" type="text" value="" data-provide="typeahead" data-source="{{users_js}}" class="form-control" /><br />
<!-- Actual customer select field (hidden) -->
<div style="display:none;">{{form.user }}</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<ul class="well">
{% for user in users %}
<li class="customer">{% spaceless %}
<img class="profile" src="{{ user.profile.profile_image_url }}" alt="Profile picture"/><br />
<span class="name">{{ user.first_name }} {{ user.last_name|slice:":1" }}</span><br />
<span class="balance">{{ user.profile.balance }}</span>
<span class="id" style="display:none;">{{ user.id }}</span>
{% endspaceless %}</li>
{% endfor %}
</ul>
</div>
</div>
</section>
<section id="amount">
<div class="row">
<div class="col-md-12">
<h2>Hvor mye?</h2>
</div>
</div>
<div class="row">
<div class="col-md-4">
{{form.amount }}
{{form.amount.errors}}<br />
</div>
</div>
</section>
<div class="row">
<div class="col-md-12">
<button type="submit" class="btn btn-lg btn-primary" name="submit_order">Sett inn</button>
<a href="{% url 'home' %}" class="btn btn-default">Avbryt</a>
</div>
</div>
</form>
{% endif %}
</section>
{% endblock %}
-102
View File
@@ -1,102 +0,0 @@
{% extends "base.html" %}
{% load static thumbnail %}
{% block active_register %}active{% endblock %}
{% block content %}
<section id="register" class="row">
{% if messages %}
<section class="row">
<div class="col-xs-4">
{% for message in messages %}
<div{% if message.tags %} class="alert alert-{{ message.tags }}"{% endif %}>{{ message }}<a class="close" data-dismiss="alert">×</a></div>
{% endfor %}
</div>
</section>
{% endif %}
<form id="order" class="form-stacked" method="post" autocomplete="off">
<fieldset>
{% csrf_token %}
<!-- Formset validation fields -->
<input type="hidden" name="form-TOTAL_FORMS" value="{{ products|length }}" id="id_form-TOTAL_FORMS" />
<input type="hidden" name="form-INITIAL_FORMS" value="{{ products|length }}" id="id_form-INITIAL_FORMS" />
<input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" />
<section id="customer">
{% if users %}
<div class="row">
<div class="col-md-12">
<h2 title="Sortert etter hvem som kjøpte noe sist.">Hvem kjøper?</h2>
</div>
</div>
<div class="row">
<div class="col-md-3">
<!-- Typeahead customer field -->
<input name="customer_name" type="text" id="id_customer_typeahead" value="" data-provide="typeahead" data-source="{{users_js}}" class="form-control" />
<!-- Actual customer select field (hidden) -->
<div style="display:none;">{{orderform.customer }}</div>
<br />
</div>
</div>
<div class="row">
<div class="col-md-12">
<ul class="well">
<li class="customer">{% spaceless %}
<img class="profile" src="{% static "dist/images/unknown_person.png" %}" alt="Profile picture" /><br />
<span class="name">Ekstern</span><br />
<span class="balance">&nbsp;</span>
{% endspaceless %}</li>
{% for user in users %}<li class="customer{% if user.profile.last_purchase_date < half_a_year_ago %} infrequent hidden{% endif %}">{% spaceless %}
<img class="profile" src="{{ user.profile.profile_image_url }}" alt="Profile picture" /><br />
<span class="name">{{ user.first_name }} {{ user.last_name|slice:":1"}}</span><br />
<span class="balance">{{ user.profile.balance }}</span>
<span class="id" style="display:none;">{{ user.id }}</span>
{% endspaceless %}</li>{% endfor %}
</ul>
<div class="users-description pull-right">
<small>Viser: De med minst <strong>{{ cheapest_product_price }}</strong> i saldo<span class="infrequent-users"> og mer enn ett kjøp siden <strong>{{ half_a_year_ago }}</strong></span>. <a href="#" class="toggle-infrequent-users">Vis alle</a></small>
</div>
</div>
</div>
{%else%}
<div class="row">
<div class="col-md-3">
<h2>Hvem kjøper?</h2>
<div class="alert alert-error">Ingen har positiv saldo i BBS.</div><a href="{% url 'deposit' %}" class="btn">Sett inn penger</a>
</div>
</div>
{%endif%}
</section>
<section id="products" class="row">
<div class="col-md-12">
<h2>Kjøper hva?</h2>
<ul class="well">
{% for product in products %}
<li id="form-{{forloop.counter0}}" class="product"><input type="hidden" name="form-{{ forloop.counter0 }}-product" value="{{product.id }}" />
<input type="hidden" id="form-{{ forloop.counter0 }}-amount" name="form-{{ forloop.counter0 }}-amount" value="0" class="span2" size="2" />
<input type="hidden" name="form-{{ forloop.counter0 }}-unit_price_int" value="{{product.sale_price_int }}" />
<input type="hidden" name="form-{{ forloop.counter0 }}-unit_price_ext" value="{{product.sale_price_ext }}" />
<span class="qty">0</span>
{% if product.image %}
<img class="product" src="{{ product.image|thumbnail_url:'product' }}" /><br />
{% else %}
<img class="product" src="{% static "dist/images/product.png" %}" /><br />
{% endif %}
<span class="name">{{ product.name }}</span><br>
<span class="sale_price_ext">{{product.sale_price_ext|floatformat:0}}</span>/<span class="sale_price_int">{{product.sale_price_int|floatformat:0}}</span>
</li>
{% endfor %}
</ul>
</div>
</section>
</fieldset>
<div class="form-actions">
<div class="col-xs-3 total">
<h2>Totalt: <span class="total_text"></span></h2>
</div>
  <button type="submit" class="btn btn-lg btn-primary" name="submit_order">Kjøp</button>
  <button type="reset" class="btn btn-default">Start på nytt</button>
</div>
</form>
</section>
{% endblock %}