Initial commit - existing project files

This commit is contained in:
W13R 2022-03-16 12:11:30 +01:00
commit c49798a9ea
82 changed files with 4304 additions and 0 deletions

View file

138
application/app/admin.py Normal file
View file

@ -0,0 +1,138 @@
#
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.views.decorators.cache import never_cache
from .models import User
from .models import Drink
from .models import Order
from .models import Global
from .models import RegisterTransaction as Register
from .forms import CustomUserChangeForm
from .forms import CustomDrinkForm
from .forms import CustomGlobalForm
from .forms import CustomRegisterTransactionForm
# Admin Site
class CustomAdminSite(admin.AdminSite):
site_header = "Drinks Administration"
site_title = "Drinks Administration"
@never_cache
def index(self, request, extra_context=None):
return super().index(request, extra_context={
"registerBalance": "{:10.2f}".format(
Global.objects.get(name="register_balance").value_float
),
"admin_info": Global.objects.get(name="admin_info").value_string,
**(extra_context or {})
})
adminSite = CustomAdminSite()
# Register your models here.
class CustomUserAdmin(UserAdmin):
model = User
form = CustomUserChangeForm
fieldsets_ = list((*UserAdmin.fieldsets,))
fieldsets_.insert(1, (
"Balance",
{"fields": ("balance", "allow_order_with_negative_balance")},
)
)
fieldsets = tuple(fieldsets_)
list_display = ["username", "balance", "is_active", "allow_order_with_negative_balance"]
def get_actions(self, request): # remove the "delete_selected" action because it breaks some functionality
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
adminSite.register(User, CustomUserAdmin)
class CustomDrinkAdmin(admin.ModelAdmin):
model = Drink
form = CustomDrinkForm
list_display = ["product_name", "content_litres", "price", "available", "binary_availability", "deleted"]
adminSite.register(Drink, CustomDrinkAdmin)
class CustomRegisterAdmin(admin.ModelAdmin):
model = Register
form = CustomRegisterTransactionForm
site_title = "Register"
list_display = ["datetime", "transaction_sum", "user", "comment"]
actions = ["delete_selected_new"]
def get_actions(self, request): # remove the "delete_selected" action because it breaks some functionality
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
def delete_selected_new(self, request, queryset):
#print(queryset)
for supply in queryset:
#print(order)
supply.delete()
if queryset.count() < 2:
self.message_user(request, f"Revoked {queryset.count()} supply.")
else:
self.message_user(request, f"Revoked {queryset.count()} supplies.")
delete_selected_new.short_description = "Revoke selected transactions"
adminSite.register(Register, CustomRegisterAdmin)
class CustomOrderAdmin(admin.ModelAdmin):
model = Order
list_display = ["product_name", "amount", "price_sum", "user", "datetime"]
actions = ["delete_selected_new"]
def get_actions(self, request): # remove the "delete_selected" action because it breaks some functionality
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
def delete_selected_new(self, request, queryset):
#print(queryset)
for order in queryset:
#print(order)
order.delete()
self.message_user(request, f"Revoked {queryset.count()} order(s).")
delete_selected_new.short_description = "Revoke selected orders"
adminSite.register(Order, CustomOrderAdmin)
class CustomGlobalAdmin(admin.ModelAdmin):
model = Global
form = CustomGlobalForm
list_display = ["name", "value_float", "value_string"]
def get_actions(self, request): # remove the "delete_selected" action because it breaks some functionality
actions = super().get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
adminSite.register(Global, CustomGlobalAdmin)

7
application/app/apps.py Normal file
View file

@ -0,0 +1,7 @@
from django.apps import AppConfig
from django.contrib.admin.apps import AdminConfig
class DAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'app'

View file

@ -0,0 +1,16 @@
from django.conf import settings
from .models import Global
def app_version(request):
try:
global_message = Global.objects.get(pk="global_message").value_string
except Global.DoesNotExist:
global_message = ""
return {
"app_version": settings.APP_VERSION,
"currency_suffix": settings.CURRENCY_SUFFIX,
"global_message": global_message
}

47
application/app/forms.py Normal file
View file

@ -0,0 +1,47 @@
from django import forms
from django.conf import settings
from django.contrib.auth.forms import UserChangeForm
from .models import User
from .models import Drink
from .models import RegisterTransaction
from .models import Global
class CustomUserChangeForm(UserChangeForm):
balance = forms.DecimalField(max_digits=8, decimal_places=2, initial=0.00, label=f"Balance {settings.CURRENCY_SUFFIX}")
class Meta:
model = User
fields = ("username", "balance")
class CustomDrinkForm(forms.ModelForm):
product_name = forms.CharField(max_length=64, label="Product Name")
content_litres = forms.DecimalField(max_digits=6, decimal_places=3, initial=0.5, label="Content (l)")
price = forms.DecimalField(max_digits=6, decimal_places=2, label=f"Price {settings.CURRENCY_SUFFIX}")
class Meta:
model = Drink
fields = ("product_name", "content_litres", "price", "binary_availability", "available", "deleted")
class CustomRegisterTransactionForm(forms.ModelForm):
class Meta:
model = RegisterTransaction
fields = ("transaction_sum", "datetime", "is_user_deposit", "comment", "user")
class CustomGlobalForm(forms.ModelForm):
comment = forms.CharField(widget=forms.Textarea, required=False)
value_float = forms.FloatField(initial=0.00)
value_string = forms.CharField(widget=forms.Textarea, required=False)
class Meta:
model = Global
fields = ("name", "comment", "value_float", "value_string")

160
application/app/models.py Normal file
View file

@ -0,0 +1,160 @@
from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django_currentuser.db.models import CurrentUserField
from django.forms import ValidationError
from django.utils import timezone
# helper
def make_register_transaction(transaction_sum:float):
regbalance = Global.objects.get(name="register_balance")
regbalance.value_float += float(round(float(transaction_sum), 2))
regbalance.save()
# Custom user model
class User(AbstractUser):
balance = models.DecimalField(max_digits=8, decimal_places=2, default=0.00)
allow_order_with_negative_balance = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
self.balance = 0
self.is_active = False
self.username = f"<deleted user #{self.pk}>"
self.last_name = ""
self.first_name = ""
self.email = ""
super().save()
#
class Drink(models.Model):
product_name = models.CharField(max_length=64)
content_litres = models.DecimalField(max_digits=6, decimal_places=3, default=0.5)
price = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
available = models.PositiveIntegerField(default=0)
deleted = models.BooleanField(default=False)
# when the following field is true:
# available > 0 -> there is a indefinetly amount of drinks left
# available < 1 -> there are no drinks left
binary_availability = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
self.deleted = True
super().save()
def __str__(self): return f"{self.product_name} ({str(self.content_litres).rstrip('0')}l) - {self.price}{settings.CURRENCY_SUFFIX}"
class RegisterTransaction(models.Model):
class Meta:
verbose_name = "register transaction"
verbose_name_plural = "register"
transaction_sum = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
# the following original_transaction_sum is needed when need to be
# updated, but the old value needs to be known (field is hidden)
old_transaction_sum = models.DecimalField(max_digits=6, decimal_places=2, default=0.00)
datetime = models.DateTimeField(default=timezone.now)
is_user_deposit = models.BooleanField(default=False)
comment = models.TextField(default=" ")
user = CurrentUserField()
def save(self, *args, **kwargs):
if self._state.adding:
make_register_transaction(self.transaction_sum)
if self.is_user_deposit == True: # update user balance
self.user.balance += self.transaction_sum
self.user.save()
self.old_transaction_sum = self.transaction_sum
super().save(*args, **kwargs)
else:
# update register transaction
sum_diff = self.transaction_sum - self.old_transaction_sum
make_register_transaction(sum_diff)
# update user balance
if self.is_user_deposit == True:
ub_sum_diff = self.transaction_sum - self.old_transaction_sum
self.user.balance += ub_sum_diff
self.user.save()
self.old_transaction_sum = self.transaction_sum
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
make_register_transaction(-self.transaction_sum)
# update user deposit
if self.is_user_deposit:
self.user.balance -= self.transaction_sum
self.user.save()
super().delete(*args, kwargs)
def __str__(self): return f"{self.transaction_sum}{settings.CURRENCY_SUFFIX} by {self.user}"
class Order(models.Model):
drink = models.ForeignKey(
"Drink",
on_delete=models.SET_NULL,
null=True,
limit_choices_to=models.Q(available__gt=0) # Query only those drinks with a availability greater than (gt) 0
)
user = CurrentUserField()
datetime = models.DateTimeField(default=timezone.now)
amount = models.PositiveIntegerField(default=1, editable=False)
# the following fields will be set automatically
# won't use foreign key, because the values of the foreign objects may change over time.
product_name = models.CharField(max_length=64, editable=False)
price_sum = models.DecimalField(max_digits=6, decimal_places=2, default=0, editable=False)
content_litres = models.DecimalField(max_digits=6, decimal_places=3, default=0, editable=False)
# TODO: Add more comments on how and why the save & delete functions are implemented
# address this in a refactoring issue.
def save(self, *args, **kwargs):
drink = Drink.objects.get(pk=self.drink.pk)
if self._state.adding and drink.available > 0:
if not drink.binary_availability:
drink.available -= self.amount
drink.save()
self.product_name = drink.product_name
self.price_sum = drink.price * self.amount
self.content_litres = drink.content_litres
self.user.balance -= self.price_sum
self.user.save()
super().save(*args, **kwargs)
else:
raise ValidationError("This entry can't be changed.")
def delete(self, *args, **kwargs):
self.user.balance += self.price_sum
self.user.save()
drink = Drink.objects.get(pk=self.drink.pk)
if not drink.binary_availability:
drink.available += self.amount
drink.save()
super().delete(*args, **kwargs)
def __str__(self): return f"{self.drink.product_name} ({str(self.drink.content_litres).rstrip('0')}l) x {self.amount} - {self.price_sum}{settings.CURRENCY_SUFFIX}"
class Global(models.Model):
# this contains global values that are generated/calculated by code
# e.g. the current balance of the register, ...
name = models.CharField(max_length=42, unique=True, primary_key=True)
comment = models.TextField()
value_float = models.FloatField(default=0.00)
value_string = models.TextField()
def __str__(self): return self.name

View file

@ -0,0 +1,137 @@
#from datetime import datetime
from django.conf import settings
from django.db import connection
def _select_from_db(sql_select:str):
result = None
with connection.cursor() as cursor:
cursor.execute(sql_select)
result = cursor.fetchall()
return result
def select_history(user, language_code="en") -> list:
# select order history and deposits
user_id = user.pk
result = _select_from_db(f"""
select
concat(
product_name, ' (',
content_litres::real, -- converting to real removes trailing zeros
'l) x ', amount, ' - ', price_sum, '{settings.CURRENCY_SUFFIX}') as "text",
datetime
from app_order
where user_id = {user_id}
union
select
concat('Deposit: +', transaction_sum, '{settings.CURRENCY_SUFFIX}') as "text",
datetime
from app_userdeposits_view
where user_id = {user_id}
order by datetime desc
fetch first 30 rows only;
""")
result = [list(row) for row in result]
if language_code == "de": # reformat for german translation
for row in result:
row[0] = row[0].replace(".", ",")
return result
def select_yopml12m(user) -> list:
# number of orders per month (last 12 months)
# only for the specified user
user_id = user.pk
result = _select_from_db(f"""
-- select the count of the orders per month (last 12 days)
select
to_char(date_trunc('month', datetime), 'YYYY-MM') as "month",
sum(amount) as "count"
from app_order
where user_id = {user_id}
and date_trunc('month', datetime) > date_trunc('month', now() - '12 months'::interval)
group by "month"
order by "month" desc;
""")
return [list(row) for row in result]
def select_aopml12m() -> list:
# number of orders per month (last 12 months)
result = _select_from_db(f"""
-- select the count of the orders per month (last 12 days)
select
to_char(date_trunc('month', datetime), 'YYYY-MM') as "month",
sum(amount) as "count"
from app_order
where date_trunc('month', datetime) > date_trunc('month', now() - '12 months'::interval)
group by "month"
order by "month" desc;
""")
return [list(row) for row in result]
def select_yopwd(user) -> list:
# number of orders per weekday (all time)
# only for the specified user
user_id = user.pk
result = _select_from_db(f"""
-- select the count of the orders per weekday (all time)
select
to_char(datetime, 'Day') as "day",
sum(amount) as "count"
from app_order
where user_id = {user_id}
group by "day"
order by "count" desc;
""")
return [list(row) for row in result]
return []
def select_aopwd() -> list:
# number of orders per weekday (all time)
result = _select_from_db(f"""
-- select the count of the orders per weekday (all time)
select
to_char(datetime, 'Day') as "day",
sum(amount) as "count"
from app_order
group by "day"
order by "count" desc;
""")
return [list(row) for row in result]
return []
def select_noyopd(user) -> list:
# number of orders per drink (all time)
# only for specified user
user_id = user.pk
result = _select_from_db(f"""
select
d.product_name as "label",
sum(o.amount) as "data"
from app_drink d
join app_order o on (d.id = o.drink_id)
where o.user_id = {user_id}
group by d.product_name
order by "data" desc;
""")
return [list(row) for row in result]
def select_noaopd() -> list:
# number of orders per drink (all time)
result = _select_from_db(f"""
select
d.product_name as "label",
sum(o.amount) as "data"
from app_drink d
join app_order o on (d.id = o.drink_id)
group by d.product_name
order by "data" desc;
""")
return [list(row) for row in result]

View file

@ -0,0 +1,18 @@
{% extends "admin/base.html" %}
{% block title %}
{% if subtitle %}
{{ subtitle }} |
{% endif %}
{{ title }} | {{ site_title|default:_('Django site admin') }}
{% endblock %}
{% block extrahead %}
<link rel="shortcut icon" href="/static/favicon.png" sizes="480x480" />
{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
{% endblock %}
{% block nav-global %}{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends "admin/index.html" %}
{% block sidebar %}
{{ block.super }}
<div>
<div>
<p>Current Register Balance: {{ registerBalance }}{{ currency_suffix }}</p>
{% if global_message != "" %}
<p>Global Message: {{ global_message }}</p>
{% endif %}
{% if admin_info != "" %}
<p>Admin Info: {{ admin_info }}</p>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,59 @@
<!DOCTYPE html>
{% load i18n %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=0.95">
<link rel="stylesheet" href="/static/css/main.css">
<link rel="shortcut icon" href="/static/favicon.png" sizes="480x480" />
<title>{% block title %}{% endblock %}</title>
{% block headAdditional %}{% endblock %}
</head>
<body>
<div class="baseLayout">
{% include "globalMessage.html" %}
{% if user.is_authenticated %}
<div class="userPanel">
{% include "userPanel.html" %}
</div>
{% endif %}
<main>
{% if user.is_authenticated or "accounts/login/" in request.path or "accounts/logout/" in request.path %}
<h1>{% block heading %}{% endblock %}</h1>
<div class="content">
{% block content %}{% endblock %}
</div>
{% else %}
<div class="centeringFlex">
{% translate "An error occured. Please log out and log in again." %}
<br>
<a href="/accounts/logout">log out</a>
</div>
{% endif %}
</main>
{% include "footer.html" %}
</div>
<script src="/static/js/main.js"></script>
</body>
</html>

View file

@ -0,0 +1,39 @@
{% extends "baseLayout.html" %}
{% load i18n %}
{% block title %}
{% translate "Drinks - Deposit" %}
{% endblock %}
{% block headAdditional %}
<link rel="stylesheet" href="/static/css/deposit.css">
{% endblock %}
{% block heading %}
{% translate "Deposit" %}
{% endblock %}
{% block content %}
<form id="depositForm">
{% csrf_token %}
<div class="row">
<div class="column">{% translate "Amount" %} {{ currency_suffix }}:</div>
<div class="column"><input type="number" name="depositAmount" id="depositAmount" max="9999.99" min="1.00"
step="0.01" autofocus></div>
</div>
<div id="statusInfo"></div>
<div class="horizontalButtonList">
<a href="/" class="button">{% translate "cancel" %}</a>
<input type="submit" id="depositSubmitBtn" class="button" value='{% translate "confirm" %}'>
</div>
</form>
<script src="/static/js/deposit.js"></script>
{% endblock %}

View file

@ -0,0 +1,6 @@
{% load i18n %}
<div class="footer">
<div>Version {{ app_version }}</div>
<div>Copyright (C) 2021, <a href="https://gitlab.com/W13R">Julian Müller (W13R)</a></div>
</div>

View file

@ -0,0 +1,5 @@
{% if global_message != "" %}
<div class="globalMessage">
<div>{{ global_message }}</div>
</div>
{% endif %}

View file

@ -0,0 +1,36 @@
{% extends "baseLayout.html" %}
{% load i18n %}
{% block title %}
{% translate "Drinks - History" %}
{% endblock %}
{% block headAdditional %}
<link rel="stylesheet" href="/static/css/history.css">
{% endblock %}
{% block heading %}
{% translate "History" %}
{% endblock %}
{% block content %}
{% if history %}
<table class="history">
<tr>
<th>{% translate "last 30 actions" %}</th>
<th></th>
</tr>
{% for h in history %}
<tr>
<td>{{ h.0 }}</td>
<td class="historyDate">{{ h.1 }}</td>
</tr>
{% endfor %}
</table>
{% else %}
{% translate "No history." %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,47 @@
{% extends "baseLayout.html" %}
{% load i18n %}
{% block title %}
{% translate "Drinks - Home" %}
{% endblock %}
{% block headAdditional %}
<link rel="stylesheet" href="/static/css/index.css">
{% endblock %}
{% block heading %}
{% translate "Available Drinks" %}
{% endblock %}
{% block content %}
{% if available_drinks %}
<ul class="availableDrinksList">
{% for drink in available_drinks %}
{% if drink.binary_availability %}
<li>
<a class="button" href="/order/{{ drink.id }}">
<span>{{ drink }}</span>
<span>{% translate "available" %}</span>
</a>
</li>
{% else %}
<li>
<a class="button" href="/order/{{ drink.id }}">
<span>{{ drink }}</span>
<span>{{ drink.available }} {% translate "available" %}</span>
</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
{% translate "No drinks available." %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,99 @@
{% extends "baseLayout.html" %}
{% load i18n %}
{% block title %}
{% translate "Drinks - Order" %}
{% endblock %}
{% block headAdditional %}
<link rel="stylesheet" href="/static/css/order.css">
<link rel="stylesheet" href="/static/css/customNumberInput.css">
{% endblock %}
{% block heading %}
{% translate "Order" %}
{% endblock %}
{% block content %}
{% if drink and drink.available > 0 and not drink.deleted %}
{% if user.balance > 0 or user.allow_order_with_negative_balance %}
<form id="orderForm">
{% csrf_token %}
<div class="row">
<div class="column">{% translate "Drink" %}:</div>
<div class="column">{{ drink.product_name }}</div>
</div>
<div class="row">
<div class="column">{% translate "Price per Item" %} ({{ currency_suffix }}):</div>
<div class="column" id="pricePerDrink" data-drink-price="{{ drink.price }}">{{ drink.price }}</div>
</div>
{% if not drink.binary_availability %}
<div class="row">
<div class="column">{% translate "Available" %}:</div>
<div class="column">{{ drink.available }}</div>
</div>
{% endif %}
<div class="row">
<div class="column">{% translate "Count" %}:</div>
<div class="column">
<span class="customNumberInput">
<button type="button" class="customNumberInput-minus" id="numberOfDrinksBtnA">-</button>
{% if drink.binary_availability %}
<input type="number" class="customNumberInputField" name="numberOfDrinks" id="numberOfDrinks"
min="1" max="100" value="1">
{% else %}
<input type="number" class="customNumberInputField" name="numberOfDrinks" id="numberOfDrinks"
max="{{ drink.available }}" min="1" max="100" value="1">
{% endif %}
<button type="button" class="customNumberInput-plus" id="numberOfDrinksBtnB">+</button>
</span>
</div>
</div>
<div class="row">
<div class="column">{% translate "Sum" %} ({{ currency_suffix }}):</div>
<div class="column" id="orderCalculatedSum">{{ drink.price }}</div>
</div>
<div id="statusInfo"></div>
<input type="hidden" name="drinkID" id="drinkID" value="{{ drink.id }}">
<div class="horizontalButtonList">
<a href="/" class="button">{% translate "cancel" %}</a>
<input type="submit" id="orderSubmitBtn" class="button" value='{% translate "order" %}'>
</div>
</form>
<script src="/static/js/order.js"></script>
<script src="/static/js/customNumberInput.js"></script>
{% else %}
<div class="centeringFlex">
<p>{% translate "You can't order this, because you have a negative balance." %}</p>
<a href="/">{% translate "back" %}</a>
</div>
{% endif %}
{% else %}
<div class="centeringFlex">
<p>{% translate "This drink is not available." %}</p>
<a href="/">{% translate "back" %}</a>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,24 @@
{% extends "baseLayout.html" %}
{% load i18n %}
{% block title %}
{% translate "Drinks - Logged Out" %}
{% endblock %}
{% block headAdditional %}
<link rel="stylesheet" href="/static/css/login.css">
{% endblock %}
{% block content %}
<div class="centeringFlex">
{% translate "Logged out! You will be redirected shortly." %}
<br><br>
<a href="/">{% translate "Click here if automatic redirection does not work." %}</a>
</div>
<script src="/static/js/logged_out.js"></script>
{% endblock %}

View file

@ -0,0 +1,91 @@
{% extends "baseLayout.html" %}
{% load i18n %}
{% block title %}
{% translate "Drinks - Login" %}
{% endblock %}
{% block headAdditional %}
<link rel="stylesheet" href="/static/css/login.css">
{% endblock %}
{% block content %}
{% if error_message %}
<p class="errorText">{{ error_message }}</p>
{% endif %}
<div class="passwordOverlayContainer nodisplay" id="passwordOverlayContainer">
<div class="passwordOverlay">
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<h1>{% translate "Log in" %}</h1>
<input type="text" name="username" autofocus="" autocapitalize="none" autocomplete="username" maxlength="150" required="" id="id_username">
<input type="password" name="password" autocomplete="current-password" required="" id="id_password" placeholder='{% translate "Password/PIN" %}'>
<div class="pinpad">
<table>
<tr>
<td><button type="button" class="pinpadBtn" data-btn="1">1</button></td>
<td><button type="button" class="pinpadBtn" data-btn="2">2</button></td>
<td><button type="button" class="pinpadBtn" data-btn="3">3</button></td>
</tr>
<tr>
<td><button type="button" class="pinpadBtn" data-btn="4">4</button></td>
<td><button type="button" class="pinpadBtn" data-btn="5">5</button></td>
<td><button type="button" class="pinpadBtn" data-btn="6">6</button></td>
</tr>
<tr>
<td><button type="button" class="pinpadBtn" data-btn="7">7</button></td>
<td><button type="button" class="pinpadBtn" data-btn="8">8</button></td>
<td><button type="button" class="pinpadBtn" data-btn="9">9</button></td>
</tr>
<tr>
<td><button type="button" class="pinpadBtn" data-btn="0">0</button></td>
<td><button type="button" class="pinpadBtn" data-btn="x">x</button></td>
<td><button type="button" class="pinpadBtn" data-btn="enter">&#9166;</button></td>
</tr>
</table>
</div>
<div class="horizontalButtonList">
<button type="button" id="pwoCancel">{% translate "cancel" %}</button>
<input class="button" id="submit_login" type="submit" value='{% translate "login" %}' />
</div>
</form>
</div>
</div>
<h1>{% translate "Choose your account" %}</h1>
<div class="userlistContainer">
<ul class="userlist">
{% for user_ in user_list %}
<li class="userlistButton button" data-username="{{ user_.username }}">
{% if user_.first_name %}
{{ user_.first_name }}
{% if user_.last_name %}
{{ user_.last_name }}
{% endif %}
{% else %}
{{ user_.username }}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<script src="/static/js/login.js"></script>
{% endblock %}

View file

@ -0,0 +1,179 @@
{% extends "baseLayout.html" %}
{% load i18n %}
{% block title %}
{% translate "Drinks - Statistics" %}
{% endblock %}
{% block headAdditional %}
<link rel="stylesheet" href="/static/css/statistics.css">
{% endblock %}
{% block heading %}
{% translate "Statistics" %}
{% endblock %}
{% block content %}
<div class="mainContainer">
<div class="dropDownMenu" id="statisticsDropDownMenu">
<button class="dropDownButton" id="statisticsDropDownMenuButton">
<div>
{% translate "Choose" %}
</div>
</button>
<div class="dropDownList">
<button class="sChoice dropDownChoice" data-statistics_div="noyopd">
{% translate "Your orders per drink" %}
</button>
<button class="sChoice dropDownChoice" data-statistics_div="yopwd">
{% translate "Your orders per weekday" %}
</button>
<button class="sChoice dropDownChoice" data-statistics_div="yopml12m">
{% translate "Your orders per month (last 12 months)" %}
</button>
<button class="sChoice dropDownChoice" data-statistics_div="noaopd">
{% translate "All orders per drink" %}
</button>
<button class="sChoice dropDownChoice" data-statistics_div="aopwd">
{% translate "All orders per weekday" %}
</button>
<button class="sChoice dropDownChoice" data-statistics_div="aopml12m">
{% translate "All orders per month (last 12 months)" %}
</button>
</div>
</div>
<div class="tablesContainer">
<div id="noyopd" class="statisticsTable nodisplay">
<h1>{% translate "Your orders per drink" %}</h1>
{% if noyopd %}
<table>
<tr>
<th>{% translate "drink" %}</th>
<th>{% translate "count" %}</th>
</tr>
{% for row in noyopd %}
<tr>
<td>{{ row.0 }}</td>
<td>{{ row.1 }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div>{% translate "No history." %}</div>
{% endif %}
</div>
<div id="noaopd" class="statisticsTable nodisplay">
<h1>{% translate "All orders per drink" %}</h1>
{% if noaopd %}
<table>
<tr>
<th>{% translate "drink" %}</th>
<th>{% translate "count" %}</th>
</tr>
{% for row in noaopd %}
<tr>
<td>{{ row.0 }}</td>
<td>{{ row.1 }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div>{% translate "No history." %}</div>
{% endif %}
</div>
<div id="yopml12m" class="statisticsTable nodisplay">
<h1>{% translate "Your orders per month (last 12 months)" %}</h1>
{% if yopml12m %}
<table>
<tr>
<th>{% translate "month" %}</th>
<th>{% translate "count" %}</th>
</tr>
{% for row in yopml12m %}
<tr>
<td>{{ row.0 }}</td>
<td>{{ row.1 }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div>{% translate "No history." %}</div>
{% endif %}
</div>
<div id="aopml12m" class="statisticsTable nodisplay">
<h1>{% translate "All orders per month (last 12 months)" %}</h1>
{% if aopml12m %}
<table>
<tr>
<th>{% translate "month" %}</th>
<th>{% translate "count" %}</th>
</tr>
{% for row in aopml12m %}
<tr>
<td>{{ row.0 }}</td>
<td>{{ row.1 }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div>{% translate "No history." %}</div>
{% endif %}
</div>
<div id="yopwd" class="statisticsTable nodisplay">
<h1>{% translate "Your orders per weekday" %}</h1>
{% if yopwd %}
<table>
<tr>
<th>{% translate "day" %}</th>
<th>{% translate "count" %}</th>
</tr>
{% for row in yopwd %}
<tr>
<td>{{ row.0 }}</td>
<td>{{ row.1 }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div>{% translate "No history." %}</div>
{% endif %}
</div>
<div id="aopwd" class="statisticsTable nodisplay">
<h1>{% translate "All orders per weekday" %}</h1>
{% if aopwd %}
<table>
<tr>
<th>{% translate "day" %}</th>
<th>{% translate "count" %}</th>
</tr>
{% for row in aopwd %}
<tr>
<td>{{ row.0 }}</td>
<td>{{ row.1 }}</td>
</tr>
{% endfor %}
</table>
{% else %}
<div>{% translate "No history." %}</div>
{% endif %}
</div>
</div>
</div>
<script src="/static/js/statistics.js"></script>
{% endblock %}

View file

@ -0,0 +1,31 @@
{% load i18n %}
<div class="dropDownMenu" id="dropDownMenu">
<button class="dropDownButton" id="dropDownMenuButton">
<div>
{% if user.first_name != "" %}
{% translate "User" %}: {{ user.first_name }} {{ user.last_name }} ({{ user.username }})
{% else %}
{% translate "User" %}: {{ user.username }}
{% endif %}
&nbsp;-&nbsp;
{% if user.balance < 0.01 %}
<span class="userBalanceWarn">{% translate "Balance" %}: {{ user.balance }}{{ currency_suffix }}</span>
{% else %}
<span>{% translate "Balance" %}: {{ user.balance }}{{ currency_suffix }}</span>
{% endif %}
</div>
</button>
<div class="dropDownList">
<a class="button dropDownChoice" id="navBarBtnHome" href="/">Home</a>
<a class="button dropDownChoice" id="navBarBtnHistory" href="/history">{% translate "History" %}</a>
<a class="button dropDownChoice" id="navBarBtnStatistics" href="/statistics">{% translate "Statistics" %}</a>
<a class="button dropDownChoice" id="navBarBtnDeposit" href="/deposit">{% translate "Deposit" %}</a>
{% if user.is_superuser %}
<a class="button dropDownChoice" href="/admin/">Admin Panel</a>
{% else %}
<a class="button dropDownChoice" href="/accounts/password_change/">{% translate "Change Password" %}</a>
{% endif %}
<a class="button dropDownChoice" href="/accounts/logout">{% translate "Logout" %}</a>
</div>
</div>

3
application/app/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

22
application/app/urls.py Normal file
View file

@ -0,0 +1,22 @@
from django.urls import path, include
from django.contrib.auth import views as auth_views
from . import views
from .admin import adminSite
urlpatterns = [
path('', views.index),
path('order/<drinkID>/', views.order),
path('history/', views.history),
path('deposit/', views.deposit),
path('statistics/', views.statistics),
path('accounts/login/', views.login_page, name="login"),
path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),
path('accounts/password_change/', auth_views.PasswordChangeView.as_view(), name='password_change'),
path('accounts/password_change_done/', views.redirect_home, name='password_change_done'),
path('admin/', adminSite.urls),
# API #
path('api/order-drink', views.api_order_drink),
path('api/deposit', views.api_deposit),
#path('api/get-statistics', views.api_get_statistics)
]

167
application/app/views.py Normal file
View file

@ -0,0 +1,167 @@
import json
import sys
from django.contrib.auth import authenticate
from django.contrib.auth import get_user_model
from django.contrib.auth import login
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import AuthenticationForm
from django.http.response import HttpResponseRedirect
from django.http.response import HttpResponse
from django.shortcuts import render
from django.utils.translation import gettext as _
from django.utils.formats import decimal
from . import sql_queries
from .models import Drink
from .models import Order
from .models import RegisterTransaction
# login view
def login_page(request):
userlist = get_user_model().objects.filter(is_superuser=False).filter(is_active=True).order_by("username")
if request.method == "POST":
form = AuthenticationForm(request.POST)
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username,password=password)
if user:
if user.is_active:
login(request, user)
return HttpResponseRedirect("/")
else:
return render(request,'registration/login.html', {
"form": form,
"user_list": userlist,
"error_message": _("Invalid username or password.")
})
else:
if request.user.is_authenticated:
return HttpResponseRedirect("/")
form = AuthenticationForm()
return render(request,'registration/login.html', {
"form": form,
"user_list": userlist
})
# actual application
@login_required
def index(request):
context = {
"available_drinks": Drink.objects.filter(available__gt=0).filter(deleted=False),
}
return render(request, "index.html", context)
@login_required
def history(request):
context = {
"history": sql_queries.select_history(request.user, language_code=request.LANGUAGE_CODE),
}
return render(request, "history.html", context)
@login_required
def order(request, drinkID):
try:
drink_ = Drink.objects.get(pk=drinkID)
context = {
"drink": drink_
}
return render(request, "order.html", context)
except Drink.DoesNotExist:
return HttpResponseRedirect("/")
@login_required
def deposit(request):
return render(request, "deposit.html", {})
@login_required
def statistics(request):
context = {
"yopml12m": sql_queries.select_yopml12m(request.user),
"aopml12m": sql_queries.select_aopml12m(),
"yopwd": sql_queries.select_yopwd(request.user),
"aopwd": sql_queries.select_aopwd(),
"noyopd": sql_queries.select_noyopd(request.user),
"noaopd": sql_queries.select_noaopd()
}
return render(request, "statistics.html", context)
@login_required
def redirect_home(request):
return HttpResponseRedirect("/")
# API for XHR requests #
@login_required
def api_order_drink(request):
# check request -> make order
user = request.user
try:
if user.allow_order_with_negative_balance or user.balance > 0:
drinkID = int(request.POST["drinkID"])
amount = int(request.POST["numberOfDrinks"])
drink = Drink.objects.get(pk=drinkID)
if ((drink.binary_availability and drink.available > 0) or (drink.available >= amount)) and not drink.deleted:
Order.objects.create(drink=drink, user=user, amount=amount)
return HttpResponse("success", status=200)
else:
return HttpResponse("notAvailable", status=400)
else: raise Exception("Balance below zero.")
except Exception as e:
print(f"An exception occured while processing an order: User: {user.username} - Exception: {e}", file=sys.stderr)
return HttpResponse(b"", status=500)
@login_required
def api_deposit(request):
# check request -> deposit
user = request.user
try:
amount = decimal.Decimal(request.POST["depositAmount"])
if 0.00 < amount < 9999.99:
# create transaction
RegisterTransaction.objects.create(
transaction_sum=amount,
comment=f"User deposit by user {user.username}",
is_user_deposit=True,
user=user
)
#
return HttpResponse("success", status=200)
else: raise Exception("Deposit amount too big or small.")
except Exception as e:
print(f"An exception occured while processing an transaction: User: {user.username} - Exception: {e}", file=sys.stderr)
return HttpResponse(b"", status=500)

View file

View file

@ -0,0 +1,16 @@
"""
ASGI config for drinks_manager project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drinks_manager.settings')
application = get_asgi_application()

View file

@ -0,0 +1,183 @@
"""
Django settings for drinks_manager project.
Generated by 'django-admin startproject' using Django 3.2.5.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.2/ref/settings/
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key secret!
django_secret_key_absolute_fp = os.environ["DJANGO_SK_ABS_FP"]
with open(django_secret_key_absolute_fp) as secret_key_file:
SECRET_KEY = secret_key_file.read().strip()
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = (os.environ["DJANGO_DEBUG"].lower() == "true")
ALLOWED_HOSTS = [
"*"
]
### CSP Configuration ###
CSP_DEFAULT_SRC = ("'self'", )
### ----------------- ###
# Application definition
INSTALLED_APPS = [
"app.apps.DAppConfig",
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
"django.middleware.locale.LocaleMiddleware",
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django_currentuser.middleware.ThreadLocalUserMiddleware",
"csp.middleware.CSPMiddleware"
]
ROOT_URLCONF = 'drinks_manager.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"app.context_processors.app_version"
],
},
},
]
WSGI_APPLICATION = 'drinks_manager.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': os.environ["PGDB_DB"],
'USER': os.environ["PGDB_USER"],
'PASSWORD': os.environ["PGDB_PASSWORD"],
'HOST': os.environ["PGDB_HOST"],
'PORT': str(os.environ["PGDB_PORT"])
}
}
CONN_MAX_AGE = 20 # keep database connections alive for n seconds
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
if os.environ["DJANGO_ENABLE_PASSWORD_VALIDATION"].lower() == "true":
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
else:
AUTH_PASSWORD_VALIDATORS = []
AUTH_USER_MODEL = "app.User"
# user will be logged out after x seconds
SESSION_COOKIE_AGE = int(os.environ["DJANGO_SESSION_COOKIE_AGE"])
# more security settings
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = os.environ["DJANGO_LANGUAGE_CODE"] # this is the default and fallback language (currently only de-de and en-us supported)
TIME_ZONE = os.environ["DJANGO_TIME_ZONE"]
USE_I18N = True
USE_L10N = True
USE_TZ = True
LOCALE_PATHS = [
BASE_DIR / "locale"
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.environ["STATIC_FILES"]
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
#
APP_VERSION = os.environ["APP_VERSION"]
try:
CURRENCY_SUFFIX = os.environ["CURRENCY_SUFFIX"]
except KeyError:
CURRENCY_SUFFIX = "$"

View file

@ -0,0 +1,21 @@
"""drinks_manager URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.urls import path, include
urlpatterns = [
path('', include("app.urls"))
]

View file

@ -0,0 +1,16 @@
"""
WSGI config for drinks_manager project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drinks_manager.settings')
application = get_wsgi_application()

Binary file not shown.

View file

@ -0,0 +1,241 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-12-22 11:07+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: app/templates/admin/base_site.html:7
msgid "Django site admin"
msgstr "Django Administrator"
#: app/templates/admin/base_site.html:15
msgid "Django administration"
msgstr "Django Administration"
#: app/templates/baseLayout.html:43
msgid "An error occured. Please log out and log in again."
msgstr "Ein Fehler ist aufgetreten. Bitte ab- und wieder anmelden."
#: app/templates/deposit.html:6
msgid "Drinks - Deposit"
msgstr "Getränke - Einzahlen"
#: app/templates/deposit.html:14 app/templates/userPanel.html:23
msgid "Deposit"
msgstr "Einzahlen"
#: app/templates/deposit.html:23
msgid "Amount"
msgstr "Summe"
#: app/templates/deposit.html:31 app/templates/order.html:71
#: app/templates/registration/login.html:56
msgid "cancel"
msgstr "Abbrechen"
#: app/templates/deposit.html:32
msgid "confirm"
msgstr "Bestätigen"
#: app/templates/history.html:6
msgid "Drinks - History"
msgstr "Getränke - Verlauf"
#: app/templates/history.html:14 app/templates/userPanel.html:21
msgid "History"
msgstr "Verlauf"
#: app/templates/history.html:22
msgid "last 30 actions"
msgstr "letzte 30 Vorgänge"
#: app/templates/history.html:33 app/templates/statistics.html:69
#: app/templates/statistics.html:89 app/templates/statistics.html:109
#: app/templates/statistics.html:129 app/templates/statistics.html:149
#: app/templates/statistics.html:169
msgid "No history."
msgstr "Kein Verlauf verfügbar."
#: app/templates/index.html:6
msgid "Drinks - Home"
msgstr "Getränke - Home"
#: app/templates/index.html:14
msgid "Available Drinks"
msgstr "Verfügbare Getränke"
#: app/templates/index.html:27 app/templates/index.html:34
msgid "available"
msgstr "verfügbar"
#: app/templates/index.html:43
msgid "No drinks available."
msgstr "Es sind gerade keine Getränke verfügbar."
#: app/templates/order.html:6
msgid "Drinks - Order"
msgstr "Getränke - Bestellen"
#: app/templates/order.html:15
msgid "Order"
msgstr "Bestellung"
#: app/templates/order.html:28
msgid "Drink"
msgstr "Getränk"
#: app/templates/order.html:33
msgid "Price per Item"
msgstr "Preis pro Getränk"
#: app/templates/order.html:39
msgid "Available"
msgstr "Verfügbar"
#: app/templates/order.html:45
msgid "Count"
msgstr "Anzahl"
#: app/templates/order.html:62
msgid "Sum"
msgstr "Summe"
#: app/templates/order.html:72
msgid "order"
msgstr "Bestellen"
#: app/templates/order.html:84
msgid "You can't order this, because you have a negative balance."
msgstr ""
"Sie können momentan keine Bestellungen aufgeben, da Sie einen negativen "
"Saldo haben."
#: app/templates/order.html:85 app/templates/order.html:94
msgid "back"
msgstr "zurück"
#: app/templates/order.html:93
msgid "This drink is not available."
msgstr "Dieses Getränk ist gerade nicht verfügbar."
#: app/templates/registration/logged_out.html:7
msgid "Drinks - Logged Out"
msgstr "Getränke - Abgemeldet"
#: app/templates/registration/logged_out.html:17
msgid "Logged out! You will be redirected shortly."
msgstr "Sie wurden abgemeldet und werden in Kürze weitergeleitet."
#: app/templates/registration/logged_out.html:19
msgid "Click here if automatic redirection does not work."
msgstr ""
"Bitte klicken Sie hier, wenn die automatische Weiterleitung nicht "
"funktionieren sollte."
#: app/templates/registration/login.html:7
msgid "Drinks - Login"
msgstr "Getränke - Anmeldung"
#: app/templates/registration/login.html:26
msgid "Log in"
msgstr "Anmelden"
#: app/templates/registration/login.html:28
msgid "Password/PIN"
msgstr "Passwort/PIN"
#: app/templates/registration/login.html:57
msgid "login"
msgstr "Anmelden"
#: app/templates/registration/login.html:65
msgid "Choose your account"
msgstr "Bitte wählen Sie Ihren Account"
#: app/templates/statistics.html:6
msgid "Drinks - Statistics"
msgstr "Getränke - Statistiken"
#: app/templates/statistics.html:15 app/templates/userPanel.html:22
msgid "Statistics"
msgstr "Statistiken"
#: app/templates/statistics.html:26
msgid "Choose"
msgstr "Auswählen"
#: app/templates/statistics.html:31 app/templates/statistics.html:54
msgid "Your orders per drink"
msgstr "Deine Bestellungen pro Getränk"
#: app/templates/statistics.html:34 app/templates/statistics.html:134
msgid "Your orders per weekday"
msgstr "Deine Bestellungen pro Wochentag"
#: app/templates/statistics.html:37 app/templates/statistics.html:94
msgid "Your orders per month (last 12 months)"
msgstr "Deine Bestellungen pro Monat (letzte 12 Monate)"
#: app/templates/statistics.html:40 app/templates/statistics.html:74
msgid "All orders per drink"
msgstr "Alle Bestellungen pro Getränk"
#: app/templates/statistics.html:43 app/templates/statistics.html:154
msgid "All orders per weekday"
msgstr "Alle Bestellungen pro Wochentag"
#: app/templates/statistics.html:46 app/templates/statistics.html:114
msgid "All orders per month (last 12 months)"
msgstr "Alle Bestellungen pro Monat (letzte 12 Monate)"
#: app/templates/statistics.html:58 app/templates/statistics.html:78
msgid "drink"
msgstr "Getränk"
#: app/templates/statistics.html:59 app/templates/statistics.html:79
#: app/templates/statistics.html:99 app/templates/statistics.html:119
#: app/templates/statistics.html:139 app/templates/statistics.html:159
msgid "count"
msgstr "Anzahl"
#: app/templates/statistics.html:98 app/templates/statistics.html:118
msgid "month"
msgstr "Monat"
#: app/templates/statistics.html:138 app/templates/statistics.html:158
msgid "day"
msgstr "Tag"
#: app/templates/userPanel.html:7 app/templates/userPanel.html:9
msgid "User"
msgstr "Benutzer"
#: app/templates/userPanel.html:13 app/templates/userPanel.html:15
msgid "Balance"
msgstr "Saldo"
#: app/templates/userPanel.html:27
msgid "Change Password"
msgstr "Passwort ändern"
#: app/templates/userPanel.html:29
msgid "Logout"
msgstr "Abmelden"
#: app/views.py:47
msgid "Invalid username or password."
msgstr "Benutzername oder Passwort ungültig."

22
application/manage.py Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drinks_manager.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()