🔥 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 import forms
from django.contrib.auth.models import User 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 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): class SimpleCreateUserForm(forms.Form):
first_name = forms.CharField(required=True, label="Fornavn") first_name = forms.CharField(required=True, label="Fornavn")
last_name = forms.CharField(required=True, label="Etternavn") last_name = forms.CharField(required=True, label="Etternavn")
email = forms.EmailField(required=True, label="E-post") 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): def save(self, commit=True):
username = format_username(self.cleaned_data["first_name"], self.cleaned_data["last_name"]) 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 datetime import datetime
from typing import Type from typing import Type
@@ -108,10 +109,10 @@ class Order(models.Model):
return self.customer is None return self.customer is None
def reciept(self): 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): 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): class OrderLine(models.Model):
@@ -125,7 +126,7 @@ class OrderLine(models.Model):
return self.amount * self.unit_price return self.amount * self.unit_price
def __str__(self): 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: class Meta:
unique_together = ("order", "product") unique_together = ("order", "product")
@@ -139,7 +140,7 @@ class Transaction(models.Model):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
def __str__(self): def __str__(self):
return "{}: {}: {}".format(self.id, self.user, self.amount) return f"{self.id}: { self.user}: {self.amount}"
class InventoryTransaction(models.Model): class InventoryTransaction(models.Model):
@@ -166,9 +167,7 @@ class InventoryTransaction(models.Model):
return self.amount * self.unit_price return self.amount * self.unit_price
def __str__(self): def __str__(self):
return "{}: {}: {}. {:.0f} stykker ({} kr per)".format( return f"{self.id}: {self.user}: {self.product}. {self.amount:.0f} stykker ({self.unit_price} kr per)"
self.id, self.user, self.product, self.amount, self.unit_price
)
class UserProfile(models.Model): class UserProfile(models.Model):
@@ -177,23 +176,25 @@ class UserProfile(models.Model):
image = models.URLField(blank=True) image = models.URLField(blank=True)
def last_purchase(self): def last_purchase(self):
if len(self.user.orders.all()) == 0: return self.user.orders.order_by("-created").first()
return None
return self.user.orders.order_by("-created")[0]
def last_purchase_date(self): def last_purchase_date(self):
last = self.last_purchase() last = self.last_purchase()
last_p = last.created if last else datetime.min last_p = last.created if last else datetime.min
return last_p return last_p
def profile_image_url(self): def image_url(self) -> str:
from django.templatetags.static import static 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: 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): 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.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.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 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 = [ 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 # TODO: migrate to SPA
# Users # Users
re_path(r"^user/create/$", create_user, name="create-user"), 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/$", inventory, name="inventory"),
re_path(r"^inventory/add/$", inventory_add, name="inventory-add"), re_path(r"^inventory/add/$", inventory_add, name="inventory-add"),
re_path(r"^report$", report, name="report"), 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 # Root level service-worker.js
urlpatterns += [re_path(r"^service-worker\.js$", serve, kwargs={"path": "dist/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 def format_username(firstname: str, lastname: str):
from django.db.models import Q
def format_username(firstname, lastname):
first = firstname.lower().strip().replace(" ", "")[:6].encode("ascii", "ignore").decode("utf-8") first = firstname.lower().strip().replace(" ", "")[:6].encode("ascii", "ignore").decode("utf-8")
last = lastname.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) return f"{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
-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) form = SimpleCreateUserForm(data=request.POST)
if form.is_valid(): if form.is_valid():
user = form.save() user = form.save()
messages.success( messages.success(request, f"Hurra! {user.first_name}{user.last_name} er lagt til")
request,
"Hurra! {} {} er lagt til".format(user.first_name, user.last_name),
)
return redirect("create-user") return redirect("create-user")
else: else:
Generated
+10 -75
View File
@@ -77,17 +77,6 @@ category = "dev"
optional = false optional = false
python-versions = ">=3.6.1" 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]] [[package]]
name = "click" name = "click"
version = "8.0.3" version = "8.0.3"
@@ -166,17 +155,6 @@ python-versions = ">=3.5"
django = ">=1.11,<4.0" django = ">=1.11,<4.0"
pillow = "*" 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]] [[package]]
name = "filelock" name = "filelock"
version = "3.4.2" version = "3.4.2"
@@ -213,7 +191,7 @@ tornado = ["tornado (>=0.2)"]
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.4.8" version = "2.4.9"
description = "File identification library for Python" description = "File identification library for Python"
category = "dev" category = "dev"
optional = false optional = false
@@ -222,14 +200,6 @@ python-versions = ">=3.7"
[package.extras] [package.extras]
license = ["ukkonen"] license = ["ukkonen"]
[[package]]
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "1.1.1" version = "1.1.1"
@@ -305,7 +275,7 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "platformdirs" 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\"." description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev" category = "dev"
optional = false optional = false
@@ -444,24 +414,6 @@ category = "dev"
optional = false optional = false
python-versions = ">=3.6" 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]] [[package]]
name = "sentinel" name = "sentinel"
version = "0.3.0" version = "0.3.0"
@@ -570,7 +522,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "tomli" name = "tomli"
version = "2.0.0" version = "2.0.1"
description = "A lil' TOML parser" description = "A lil' TOML parser"
category = "dev" category = "dev"
optional = false optional = false
@@ -618,7 +570,7 @@ testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)",
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "63af070a3bfe76cd32929214c5162596f6d693e65e88370a480237ecd8885113" content-hash = "3fa14cb7bc28f810082b1aa4604a74060ec4ea32e63864f91a005aa37ea73c8c"
[metadata.files] [metadata.files]
asgiref = [ asgiref = [
@@ -670,10 +622,6 @@ cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, {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 = [ click = [
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, {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.tar.gz", hash = "sha256:a7dd9cf97efaf70ba5d76484a962f08ba65b31f1681bc417257743650e9e8a8a"},
{file = "easy_thumbnails-2.7.2-py3-none-any.whl", hash = "sha256:9d7ed56a9b57aba7a8d0e03dc3b8507ca51aab5e0ed0bc705c3261ff1f659192"}, {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 = [ filelock = [
{file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"},
{file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, {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"}, {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
] ]
identify = [ identify = [
{file = "identify-2.4.8-py2.py3-none-any.whl", hash = "sha256:a55bdd671b6063eb837af938c250ec00bba6e610454265133b0d2db7ae718d0f"}, {file = "identify-2.4.9-py2.py3-none-any.whl", hash = "sha256:bff7c4959d68510bc28b99d664b6a623e36c6eadc933f89a4e0a9ddff9b4fee4"},
{file = "identify-2.4.8.tar.gz", hash = "sha256:97e839c1779f07011b84c92af183e1883d9745d532d83412cca1ca76d3808c1c"}, {file = "identify-2.4.9.tar.gz", hash = "sha256:e926ae3b3dc142b6a7a9c65433eb14ccac751b724ee255f7c2ed3b5970d764fb"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
] ]
iniconfig = [ iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {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"}, {file = "Pillow-9.0.1.tar.gz", hash = "sha256:6c8bc8238a7dfdaf7a75f5ec5a663f4173f8c367e5a39f87e720495e1eed75fa"},
] ]
platformdirs = [ platformdirs = [
{file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, {file = "platformdirs-2.5.0-py3-none-any.whl", hash = "sha256:30671902352e97b1eafd74ade8e4a694782bd3471685e78c32d0fdfd3aa7e7bb"},
{file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, {file = "platformdirs-2.5.0.tar.gz", hash = "sha256:8ec11dfba28ecc0715eb5fb0147a87b1bf325f349f3da9aab2cd6b50b96b692b"},
] ]
pluggy = [ pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {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-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, {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 = [ sentinel = [
{file = "sentinel-0.3.0-py3-none-any.whl", hash = "sha256:bd8710dd26752039c668604f6be2aaf741b56f7811c5924a4dcdfd74359244f3"}, {file = "sentinel-0.3.0-py3-none-any.whl", hash = "sha256:bd8710dd26752039c668604f6be2aaf741b56f7811c5924a4dcdfd74359244f3"},
{file = "sentinel-0.3.0.tar.gz", hash = "sha256:f28143aa4716dbc8f6193f5682176a3c33cd26aaae05d9ecf66c186a9887cc2d"}, {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"}, {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
] ]
tomli = [ tomli = [
{file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
] ]
typing-extensions = [ typing-extensions = [
{file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, {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-extensions = "^3.1.3"
Django = "^3.2.8" Django = "^3.2.8"
easy-thumbnails = "2.7.2" easy-thumbnails = "2.7.2"
facebook-sdk = "3.0.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
mysqlclient = "^2.0.3" mysqlclient = "^2.0.3"
Pillow = "^9.0.0" 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 %}