"""
views.py - establish the views (pages) for the F. P. I. web application.
"""
import time
from django.contrib import messages
from collections import OrderedDict
from csv import writer as csv_writer
from enum import Enum
from http import HTTPStatus
from json import loads
from logging import getLogger, \
debug, \
info
from operator import \
methodcaller
from string import digits
from typing import \
Any, \
Dict, \
List, \
Optional, \
Type, \
Tuple, \
Union
from django.conf import settings
from django.contrib.auth import \
authenticate, \
get_user_model, \
login, \
logout, \
update_session_auth_hash
from django.contrib.auth.mixins import \
LoginRequiredMixin, \
PermissionRequiredMixin
from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import ValidationError
from django.core.serializers import serialize
from django.db import transaction
from django.db.models import ProtectedError
from django.db.models.functions import Substr
from django.forms import formset_factory
from django.http import \
HttpResponse, \
JsonResponse, \
StreamingHttpResponse, \
HttpResponseRedirect
from django.shortcuts import \
redirect, \
render
from django.urls import \
reverse, \
reverse_lazy
from django.utils import timezone
from django.views import View
from django.views.generic import \
CreateView, \
DeleteView, \
DetailView, \
FormView, \
ListView, \
TemplateView, \
UpdateView
# from constants import UserInfo
from fpiweb.constants import \
ProjectError, \
UserInfo, \
TargetUser, \
AccessLevel, \
AccessDict
from fpiweb.models import \
Activity, \
Box, \
BoxType, \
BoxNumber, \
Constraints, \
LocRow, \
LocBin, \
LocTier, \
Pallet, \
Product, \
Profile, \
Location, \
PalletBox, \
ProductCategory, \
ProductExample
from fpiweb.code_reader import \
CodeReaderError, \
read_box_number
from fpiweb.forms import \
BoxItemForm, \
BoxTypeForm, \
BoxTypeMaintenanceForm, \
ConfirmMergeForm, \
ConstraintsForm, \
BuildPalletForm, \
EmptyBoxNumberForm, \
ExistingBoxTypeForm, \
ExistingLocationForm, \
ExistingLocationWithBoxesForm, \
ExistingProductForm, \
ExtantBoxNumberForm, \
ExpYearForm, \
FilledBoxNumberForm, \
HiddenPalletForm, \
LocRowForm, \
LocBinForm, \
LocTierForm, \
MoveToLocationForm, \
NewBoxForm, \
NewBoxNumberForm, \
PalletNameForm, \
PalletSelectForm, \
ProductForm, \
ExpMoStartForm, \
ExpMoEndForm, \
validation_exp_months_bool, \
UserInfoForm, \
UserInfoModes as MODES, \
ProductCategoryForm, \
ProductNameForm, \
ProductExampleForm, \
ManualLocTableForm
from fpiweb.support.BoxManagement import \
BoxManagementClass
from fpiweb.support.PermissionsManagement import \
ManageUserPermissions
__author__ = '(Multiple)'
__project__ = "Food-Pantry-Inventory"
__creation_date__ = "04/01/2019"
logger = getLogger('fpiweb')
[docs]def error_page(
request,
message=None,
message_list=tuple(),
status=HTTPStatus.BAD_REQUEST):
context = {
'message': message,
'message_list': message_list,
}
context = add_navbar_vars(request.user, context)
return render(
request,
'fpiweb/error.html',
context,
status=status
)
[docs]def add_navbar_vars(user: Optional, context: Dict) -> Dict:
"""
Add context variables needed by the navigation bar.
:param user: user record (usually extracted from the request object
:param context: current context
:return: modified context
"""
if user:
user_info = ManageUserPermissions().get_user_info(user.id)
else:
fake_user = dict()
fake_user['first_name'] = ''
fake_user['last_name'] = ''
fake_user['highest_access_level'] = AccessLevel.No_Access
fake_profile = dict()
fake_profile['title'] = ""
user_info = UserInfo(
user=fake_user,
profile=fake_profile,
highest_access_level=fake_user['highest_access_level'],
is_active=False,
is_superuser=False,
)
context['user_info'] = user_info
context['access_level'] = AccessLevel
context['user_access'] = user_info.highest_access_level
return context
[docs]def get_user_and_profile(request):
user = request.user
profile = user.profile or Profile.objects.create(user=user)
return user, profile
[docs]class IndexView(LoginRequiredMixin, TemplateView):
"""
Default web page (/index)
"""
template_name = 'fpiweb/index.html'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class AboutView(TemplateView):
"""
The About View for this application.
"""
template_name = 'fpiweb/about.html'
context_object_name = 'about_context'
[docs] def get_context_data(self, **kwargs):
"""
Add Site Information to About page.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
# get this information from the database later
site_name = 'WARM (Westerville Area Resource Ministries)'
site_address = '150 Heatherdown Dr.'
site_csz = 'Westerville Ohio 43081'
site_phone = '614-899-0196'
site_url = 'http://www.warmwesterville.org'
context['site_name'] = site_name
context['site_address'] = site_address
context['site_csz'] = site_csz
context['site_phone'] = site_phone
context['site_url'] = site_url
return context
[docs]class ConfirmPasswordChangeView(LoginRequiredMixin, View):
"""
Confirm the password was successfully changed.
"""
template_name = 'fpiweb/confirm_password_change.html'
success_url = reverse_lazy('fpiweb:index')
[docs] def get(self, request, *args, **kwargs):
"""
Add user info for the template.
:param request:
:param args:
:param kwargs:
:return:
"""
context = dict()
context = add_navbar_vars(self.request.user, context)
return render(request, self.template_name, context)
[docs]class MaintenanceView(PermissionRequiredMixin, TemplateView):
"""
Default web page (/index)
"""
permission_required = (
'fpiweb.view_system_maintenance',
)
template_name = 'fpiweb/system_maintenance.html'
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocRowListView(PermissionRequiredMixin, ListView):
"""
List of existing rows using a generic ListView.
"""
permission_required = (
'fpiweb.view_locrow',
)
model = LocRow
template_name = 'fpiweb/loc_row_list.html'
context_object_name = 'loc_row_list_content'
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocRowCreateView(PermissionRequiredMixin, CreateView):
"""
Create a row using a generic CreateView.
"""
permission_required = (
'fpiweb.add_locrow',
)
model = LocRow
template_name = 'fpiweb/loc_row_edit.html'
context_object_name = 'loc_row'
success_url = reverse_lazy('fpiweb:loc_row_view')
form_class = LocRowForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocRowUpdateView(PermissionRequiredMixin, UpdateView):
"""
Update a row using a generic UpdateView.
"""
permission_required = (
'fpiweb.change_locrow',
)
model = LocRow
template_name = 'fpiweb/loc_row_edit.html'
context_object_name = 'loc_row'
form_class = LocRowForm
success_url = reverse_lazy('fpiweb:loc_row_view')
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocRowDeleteView(PermissionRequiredMixin, DeleteView):
"""
Delete a row using a generic DeleteView.
"""
permission_required = (
'fpiweb.delete_locrow',
)
model = LocRow
template_name = 'fpiweb/loc_row_delete.html'
context_object_name = 'loc_row'
success_url = reverse_lazy('fpiweb:loc_row_view')
form_class = LocRowForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_row_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocBinListView(PermissionRequiredMixin, ListView):
"""
List of existing bins using a generic ListView.
"""
permission_required = (
'fpiweb.view_locbin',
)
model = LocBin
template_name = 'fpiweb/loc_bin_list.html'
context_object_name = 'loc_bin_list_content'
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_row_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocBinCreateView(PermissionRequiredMixin, CreateView):
"""
Create a bin using a generic CreateView.
"""
permission_required = (
'fpiweb.add_locbin',
)
model = LocBin
template_name = 'fpiweb/loc_bin_edit.html'
context_object_name = 'loc_bin'
success_url = reverse_lazy('fpiweb:loc_bin_view')
form_class = LocBinForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_row_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocBinUpdateView(PermissionRequiredMixin, UpdateView):
"""
Update a bin using a generic UpdateView.
"""
permission_required = (
'fpiweb.change_locbin',
)
model = LocBin
template_name = 'fpiweb/loc_bin_edit.html'
context_object_name = 'loc_bin'
success_url = reverse_lazy('fpiweb:loc_bin_view')
form_class = LocBinForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_bin_update',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocBinDeleteView(PermissionRequiredMixin, DeleteView):
"""
Delete a bin using a generic DeleteView.
"""
permission_required = (
'fpiweb.delete_locbin',
)
model = LocBin
template_name = 'fpiweb/loc_bin_delete.html'
context_object_name = 'loc_bin'
success_url = reverse_lazy('fpiweb:loc_bin_view')
form_class = LocBinForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_bin_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocTierListView(PermissionRequiredMixin, ListView):
"""
List of existing tiers using a generic ListView.
"""
permission_required = (
'fpiweb.view_loctier',
)
model = LocTier
template_name = 'fpiweb/loc_tier_list.html'
context_object_name = 'loc_tier_list_content'
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_row_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocTierCreateView(PermissionRequiredMixin, CreateView):
"""
Create a tier using a generic CreateView.
"""
permission_required = (
'fpiweb.add_loctier',
)
model = LocTier
template_name = 'fpiweb/loc_tier_edit.html'
context_object_name = 'loc_tier'
success_url = reverse_lazy('fpiweb:loc_tier_view')
form_class = LocTierForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_row_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocTierUpdateView(PermissionRequiredMixin, UpdateView):
"""
Update a tier using a generic UpdateView.
"""
permission_required = (
'fpiweb.change_loctier',
)
model = LocTier
template_name = 'fpiweb/loc_tier_edit.html'
context_object_name = 'loc_tier'
success_url = reverse_lazy('fpiweb:loc_tier_view')
form_class = LocTierForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_tier_update',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class LocTierDeleteView(PermissionRequiredMixin, DeleteView):
"""
Delete a tier using a generic DeleteView.
"""
permission_required = (
'fpiweb.delete_loctier',
)
model = LocTier
template_name = 'fpiweb/loc_tier_delete.html'
context_object_name = 'loc_tier'
success_url = reverse_lazy('fpiweb:loc_tier_view')
form_class = LocTierForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_tier_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ConstraintsListView(PermissionRequiredMixin, ListView):
"""
List of existing constraints.
"""
permission_required = (
'fpiweb.view_constraints',
)
model = Constraints
template_name = 'fpiweb/constraints_list.html'
context_object_name = 'constraints_list_content'
[docs] def get_context_data(self, *, object_list=None, **kwargs):
"""
Add additional content to the context dictionary.
:param object_list:
:param kwargs:
:return:
"""
context = super(ConstraintsListView, self).get_context_data()
# provide additional information to the template
INT_RANGE = Constraints.INT_RANGE
CHAR_RANGE = Constraints.CHAR_RANGE
range_list = [INT_RANGE, CHAR_RANGE]
context['range_list'] = range_list
context = add_navbar_vars(self.request.user, context)
logger.info(
f'Constraint extra info: INT_RANGE: {INT_RANGE}, '
f'CHAR__RANGE: '
f'{CHAR_RANGE}, range_list: {range_list}'
)
return context
[docs]class ConstraintCreateView(PermissionRequiredMixin, CreateView):
"""
Create a constraint using a generic CreateView.
"""
permission_required = (
'fpiweb.add_constraints',
)
model = Constraints
template_name = 'fpiweb/constraint_edit.html'
context_object_name = 'constraints'
success_url = reverse_lazy('fpiweb:constraints_view')
formClass = ConstraintsForm
fields = ['constraint_name', 'constraint_descr', 'constraint_type',
'constraint_min', 'constraint_max', 'constraint_list', ]
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:loc_row_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ConstraintUpdateView(PermissionRequiredMixin, UpdateView):
"""
Update a constraint using a generic UpdateView.
"""
permission_required = (
'fpiweb.change_constraints',
)
model = Constraints
template_name = 'fpiweb/constraint_edit.html'
context_object_name = 'constraints'
success_url = reverse_lazy('fpiweb:constraints_view')
form_class = ConstraintsForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:constraint_update',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ConstraintDeleteView(PermissionRequiredMixin, DeleteView):
"""
Delete a constraint using a generic DeleteView.
"""
permission_required = (
'fpiweb.delete_constraints',
)
model = Constraints
template_name = 'fpiweb/constraint_delete.html'
context_object_name = 'constraints'
success_url = reverse_lazy('fpiweb:constraints_view')
form_class = ConstraintsForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:constraint_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BoxNewView(PermissionRequiredMixin, View):
# model = Box
permission_required = (
'fpiweb.dummy_profile',
)
template_name = 'fpiweb/box_new.html'
# context_object_name = 'box'
# form_class = NewBoxForm
# def get_success_url(self):
# return reverse(
# 'fpiweb:box_details',
# args=(self.object.pk,)
# )
[docs] def get(self, request, *args, **kwargs):
"""
Prepare to present the new box view.
:param request:
:param args:
:param kwargs:
:return:
"""
box_number = kwargs.get('box_number')
if not box_number:
return error_page(request, 'missing box_number')
if not BoxNumber.validate(box_number):
return error_page(
request,
"Invalid box_number '{}'".format(box_number),
)
new_box_form = NewBoxForm(initial={'box_number': box_number})
context = {'form': new_box_form}
context = add_navbar_vars(self.request.user, context)
return render(
request,
self.template_name,
context,
)
[docs] def post(self, request, *args, **kwargs):
box_number = kwargs.get('box_number')
if not box_number:
return error_page(request, 'missing box_number')
if not BoxNumber.validate(box_number):
return error_page(
request,
"Invalid box_number '{}'".format(box_number),
)
new_box_form = NewBoxForm(
request.POST,
initial={'box_number': box_number},
)
if not new_box_form.is_valid():
context = {
'form': new_box_form,
}
context = add_navbar_vars(request.user, context)
return render(
request,
self.template_name,
context,
)
box = new_box_form.save()
return redirect(reverse(
'fpiweb:box_details', args=(box.pk,)
))
[docs]class BoxEditView(PermissionRequiredMixin, UpdateView):
permission_required = (
'fpiweb.dummy_profile',
)
model = Box
template_name = 'fpiweb/box_edit.html'
context_object_name = 'box'
form_class = NewBoxForm
success_url = reverse_lazy('fpiweb:index')
[docs]class BoxDetailsView(PermissionRequiredMixin, DetailView):
permission_required = (
'fpiweb.dummy_profile',
)
model = Box
template_name = 'fpiweb/box_detail.html'
context_object_name = 'box'
[docs] def get_context_data(self, **kwargs):
logger.debug(f"kwargs are {kwargs}")
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BoxEmptyMoveView(PermissionRequiredMixin, TemplateView):
permission_required = (
'fpiweb.dummy_profile',
)
template_name = 'fpiweb/box_empty_move.html'
[docs] def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BoxMoveView(PermissionRequiredMixin, TemplateView):
permission_required = (
'fpiweb.dummy_profile',
)
template_name = 'fpiweb/box_empty_move.html'
[docs] def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BoxEmptyView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.dummy_profile',
)
[docs]class BoxScannedView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.dummy_profile',
)
[docs] def get(self, request, **kwargs):
box_number = kwargs.get('number')
if box_number is None:
return error_page(request, "missing kwargs['number']")
box_number = BoxNumber.format_box_number(box_number)
try:
box = Box.objects.get(box_number=box_number)
except Box.DoesNotExist:
return redirect('fpiweb:box_new', box_number=box_number)
return redirect('fpiweb:build_pallet', args=(box.pk,))
[docs]class TestScanView(PermissionRequiredMixin, TemplateView):
permission_required = (
'fpiweb.dummy_profile',
)
template_name = 'fpiweb/test_scan.html'
[docs] @staticmethod
def get_box_scanned_url(box_number):
if box_number.lower().startswith('box'):
box_number = box_number[3:]
return reverse('fpiweb:box_scanned', args=(box_number,))
[docs] @staticmethod
def get_box_url_by_filters(**filters):
box_number = Box.objects \
.filter(**filters) \
.values_list('box_number', flat=True) \
.first()
if box_number is None:
return ""
return TestScanView.get_box_scanned_url(box_number)
[docs] def get_context_data(self, **kwargs):
full_box_url = self.get_box_url_by_filters(product__isnull=False)
empty_box_url = self.get_box_url_by_filters(product__isnull=True)
new_box_url = self.get_box_scanned_url(
BoxNumber.get_next_box_number()
)
# schema http or https
schema = 'http'
if settings.DEBUG is False and hasattr(self.request, 'schema'):
schema = self.request.schema
protocol_and_host = "{}://{}".format(
schema,
self.request.META.get('HTTP_HOST', '')
)
full_box_url = protocol_and_host + full_box_url
empty_box_url = protocol_and_host + empty_box_url
new_box_url = protocol_and_host + new_box_url
empty_box = Box.objects.filter(product__isnull=True).first()
full_box = Box.objects.filter(product__isnull=False).first()
# load up the context with results
context = super().get_context_data(**kwargs)
context = {
'full_box_url': full_box_url,
'empty_box_url': empty_box_url,
'new_box_url': new_box_url,
'empty_box': empty_box,
'full_box': full_box,
'next_box_number': BoxNumber.get_next_box_number(),
}
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BuildPalletError(RuntimeError):
pass
[docs]class BuildPalletView(PermissionRequiredMixin, View):
"""
Manage preparing a pallet of bxes of product to store in the warehouse.
"""
permission_required = (
'fpiweb.build_pallet',
)
form_template = 'fpiweb/build_pallet.html'
confirmation_template = 'fpiweb/build_pallet_confirmation.html'
build_pallet_form_prefix = 'build_pallet'
formset_prefix = 'box_forms'
hidden_pallet_form_prefix = 'pallet'
page_title = 'Build Pallet'
BoxFormFactory = formset_factory(
BoxItemForm,
extra=0,
)
PALLET_SELECT_FORM_NAME = 'pallet_select_form'
PALLET_NAME_FORM_NAME = 'pallet_name_form'
[docs] @staticmethod
def show_pallet_management_page(
request,
pallet_select_form=None,
pallet_name_form=None,
status_code=HTTPStatus.OK):
"""
Prepare info to name or select a pallet.
Note that this is invoking show_page in a different class.
:param request: Info from Django
:param pallet_select_form: Form to use to select an existing pallet
:param pallet_name_form: form to use to name a new pallet
:param status_code: status code to send to browser
:return: prepated web page
"""
return PalletManagementView.show_page(
request,
page_title=BuildPalletView.page_title,
prompt="Select an existing pallet or create a new one to continue",
show_delete=False,
pallet_select_form=pallet_select_form,
pallet_name_form=pallet_name_form,
status_code=status_code,
)
[docs] def get(self, request):
"""
Show page to select/add new pallet.
This page will POST back to this view.
:param request: info from Django about this page to display
:return: prepared web page
"""
return self.show_pallet_management_page(request)
[docs] def post(self, request):
# BuildPalletView.post
logger.debug(f"POST data is {request.POST}")
# The forms in pallet_management.html have a form_name input to help
# us sort out which form is being submitted.
form_name = request.POST.get('form_name')
if form_name is None:
# Processing contents of build_pallet.html
return self.process_build_pallet_forms(request)
if form_name not in [
self.PALLET_SELECT_FORM_NAME,
self.PALLET_NAME_FORM_NAME
]:
message = f"Unexpected form name {repr(form_name)}"
logger.error(message)
return error_page(
request,
message=message,
status=HTTPStatus.INTERNAL_SERVER_ERROR,
)
pallet = None
if form_name == self.PALLET_SELECT_FORM_NAME:
pallet_select_form = PalletSelectForm(request.POST)
if not pallet_select_form.is_valid():
return self.show_pallet_management_page(
request,
pallet_select_form=pallet_select_form,
status_code=HTTPStatus.BAD_REQUEST,
)
pallet = pallet_select_form.cleaned_data.get('pallet')
pallet.pallet_status = Pallet.FILL
if form_name == self.PALLET_NAME_FORM_NAME:
pallet_name_form = PalletNameForm(request.POST)
if not pallet_name_form.is_valid():
return self.show_pallet_management_page(
request,
pallet_name_form=pallet_name_form,
status_code=HTTPStatus.BAD_REQUEST,
)
pallet_name_form.instance.pallet_status = Pallet.FILL
pallet = pallet_name_form.save()
if not pallet:
message = f"pallet not set"
logger.error(message)
return error_page(
request,
message=message,
status=HTTPStatus.INTERNAL_SERVER_ERROR,
)
# Load boxes (PalletBox records) for pallet
pallet_boxes = pallet.boxes.all()
initial_data = []
for pallet_box in pallet_boxes:
form_data = {
'box_number': pallet_box.box_number,
'product': pallet_box.product,
'exp_year': pallet_box.exp_year,
'exp_month_start': pallet_box.exp_month_start,
'exp_month_end': pallet_box.exp_month_end,
}
logger.debug("adding {} to initial_data".format(form_data))
initial_data.append(form_data)
build_pallet_form = BuildPalletForm(
prefix=self.build_pallet_form_prefix,
instance=pallet.location,
)
box_forms = self.BoxFormFactory(
initial=initial_data,
prefix=self.formset_prefix,
)
pallet_form = HiddenPalletForm(
prefix=self.hidden_pallet_form_prefix,
initial={
'pallet': pallet,
},
)
return self.show_forms_response(
request,
build_pallet_form,
box_forms,
pallet_form,
status=HTTPStatus.OK,
)
[docs] @staticmethod
def prepare_pallet_and_pallet_boxes(
pallet_form,
build_pallet_form,
box_forms,
):
location = build_pallet_form.instance
pallet = pallet_form.cleaned_data.get('pallet')
pallet.location = location
pallet.pallet_status = Pallet.FILL
pallet.save()
# Update box records and
boxes_by_box_number = OrderedDict()
duplicate_box_numbers = set()
for i, box_form in enumerate(box_forms):
cleaned_data = box_form.cleaned_data
if not cleaned_data:
continue
box_number = cleaned_data.get('box_number')
# Is this a duplicate box_id?
if box_number in boxes_by_box_number:
duplicate_box_numbers.add(box_number)
continue
# Is box_number present in database?
try:
pallet_box = PalletBox.objects.get(
pallet=pallet,
box_number=box_number
)
logger.debug(f"found existing box {box_number}")
except PalletBox.DoesNotExist:
pallet_box = PalletBox(
box_number=box_number,
)
logger.debug(f"Created new box {box_number}")
pallet_box.pallet = pallet
pallet_box.product = cleaned_data.get('product')
pallet_box.exp_year = cleaned_data.get('exp_year')
pallet_box.exp_month_start = cleaned_data.get('exp_month_start', 0)
pallet_box.exp_month_end = cleaned_data.get('exp_month_end', 0)
# Does the pallet_box have a box?
if not pallet_box.box:
box, created = Box.objects.get_or_create(
box_number=box_number,
box_type=Box.box_type_default(),
)
pallet_box.box = box
pallet_box.save()
boxes_by_box_number[box_number] = pallet_box
# When the box was scanned it would have been emptied if it was
# filled. This catches whether anything has changed.
if pallet_box.box.is_filled():
box_management = BoxManagementClass()
box_management.box_consume(pallet_box.box)
if duplicate_box_numbers:
duplicate_box_numbers = [str(k) for k in duplicate_box_numbers]
message = f"Duplicate box numbers: " \
f"{', '.join(duplicate_box_numbers)}"
logger.debug(message)
build_pallet_form.add_error(None, message)
raise BuildPalletError(message)
return pallet, location, boxes_by_box_number
[docs]class ScannerViewError(RuntimeError):
pass
[docs]class ScannerView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.dummy_profile',
)
[docs] @staticmethod
def response(
success,
data=None,
errors=None, status=HTTPStatus.OK,
):
return JsonResponse(
{
'success': success,
'data': data if data else {},
'errors': errors if errors else [],
},
status=status
)
[docs] @staticmethod
def error_response(errors, status=HTTPStatus.BAD_REQUEST):
return ScannerView.response(
False,
errors,
status=status
)
[docs] @staticmethod
def get_keyed_in_box_number(box_number):
"""
:param box_number: the box number (a string), may be None
:return:
"""
box_number = box_number or ''
if not box_number:
return None
# strip out everything, but digits
box_number = "".join(c for c in box_number if c in digits)
if not box_number:
return None
try:
box_number = int(box_number)
except ValueError:
return None
return BoxNumber.format_box_number(box_number)
[docs] @staticmethod
def get_box(scan_data=None, box_number=None):
if not scan_data and not box_number:
raise ScannerViewError('missing scan_data and box_number')
if not box_number:
try:
box_number = read_box_number(scan_data)
except CodeReaderError as cre:
raise ScannerViewError(str(cre))
default_box_type = Box.box_type_default()
box, created = Box.objects.get_or_create(
box_number=box_number,
defaults={
'box_type': default_box_type,
'quantity': default_box_type.box_type_qty,
}
)
if created:
logger.info(f"Box with box number {box_number} created.")
else:
logger.info(f"Found box with box number {box_number}.")
return box, created
[docs] @staticmethod
def get_box_data(scan_data=None, box_number=None):
box, created = ScannerView.get_box(
scan_data=scan_data,
box_number=box_number
)
# serialize works on an iterable of objects and returns a string
# loads returns a list of dicts
box_dicts = loads(serialize("json", [box]))
data = {
'box': box_dicts[0],
'box_meta': {
'is_new': created,
}
}
return data
[docs] def post(self, request, *args, **kwargs):
scan_data = request.POST.get('scanData')
box_number = self.get_keyed_in_box_number(
request.POST.get('boxNumber'),
)
try:
box_data = self.get_box_data(scan_data, box_number)
except ScannerViewError as sve:
error_message = str(sve)
logger.error(error_message)
return self.error_response([error_message])
return self.response(
True,
data=box_data,
status=HTTPStatus.OK,
)
[docs]class MANUAL_NOTICE_TYPE(Enum):
"""
Manual generic notice type.
"""
NOTICE: str = 'NOTICE'
QUESTION: str = 'QUESTION'
[docs]def manual_generic_notification(
request,
note_type: MANUAL_NOTICE_TYPE,
title: str = None,
message_list: tuple = tuple(),
action_message: str = '',
yes_url: str = 'fpiweb:about',
no_url: str = 'fpiweb:about',
return_url: str = 'fpiweb:about',
status: int = HTTPStatus.OK,
):
"""
Provide a generic notification screen for the manual box subsystem.
:param request: request info from calling view
:param note_type: type of notice (error or note)
:param title: title to use for notification
:param message_list: List of lines to display in notification
:param action_message: final message or question
:param yes_url: if question, url for yes action
:param no_url: if question, url for no action
:param return_url: if notice, url to go to after notification
:param status: status code to flag notification if needed
:return:
"""
# request simply passed on to render
# template name set to fpiweb:manual_generic_notification
# context: build contest for template
context = dict()
context['type'] = note_type
context['title'] = title
context['message_list'] = message_list
context['action_message'] = action_message
context['yes_url'] = yes_url
context['no_url'] = no_url
context['return_url'] = return_url
context = add_navbar_vars(request.user, context)
# content_type: use response status from HTTPStatus
template_info = render(
request,
'fpiweb/manual_generic_notification.html',
context=context,
status=status,
)
return template_info
[docs]class ManualPalletStatus(PermissionRequiredMixin, ListView):
"""
Show the status of a pallet.
"""
permission_required = (
'fpiweb.dummy_profile',
)
model = Pallet
template_name = 'fpiweb/manual_pallet_status.html'
context_object_name = 'manual_pallet_status'
success_url = reverse_lazy('fpiweb:index')
[docs] def get_(self, **kwargs):
"""
Add Site Information to About page.
:param kwargs:
:return:
"""
# get current context data
context = super().get_context_data(**kwargs)
# get related stuff from the database
current_user = self.request.user
profile_rec = Profile.objects.get(user_id=current_user.id)
if profile_rec.active_pallet:
pallet_rec = Pallet.objects.select_related(
'location',
'location__loc_row',
'location__loc_bin',
'location__loc_tier',
).get(id=profile_rec.active_pallet)
location_rec = pallet_rec.location
loc_row_rec = location_rec.loc_row
loc_bin_rec = location_rec.loc_bin
loc_tier_rec = location_rec.loc_tier
box_set = PalletBox.objects.filter(
pallet_id=pallet_rec.id).order_by('box_number')
else:
pallet_rec = None
location_rec = None
loc_row_rec = None
loc_bin_rec = None
loc_tier_rec = None
box_set = None
context['user'] = current_user
context['profile'] = profile_rec
context['active_pallet'] = pallet_rec
context['location'] = location_rec
context['loc_row'] = loc_row_rec
context['loc_bin'] = loc_bin_rec
context['loc_tier'] = loc_tier_rec
context['box_set'] = box_set
context = add_navbar_vars(current_user, context)
return context
[docs]class ManualPalletMoveView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.move_pallet',
)
MODE_ENTER_FROM_LOCATION = 'enter from location'
MODE_ENTER_TO_LOCATION = 'enter to location'
MODE_CONFIRM_MERGE = 'confirm merge'
MODE_COMPLETE = 'complete'
FORM_PREFIX_FROM_LOCATION = 'from'
FORM_PREFIX_TO_LOCATION = 'to'
FORM_PREFIX_CONFIRM_MERGE = 'confirm_merge'
template = 'fpiweb/manual_pallet_move.html'
[docs] def get(self, request):
return self.build_response(
request,
self.MODE_ENTER_FROM_LOCATION,
from_location_form=ExistingLocationWithBoxesForm(
prefix=self.FORM_PREFIX_FROM_LOCATION,
)
)
[docs] def post(self, request):
mode = request.POST.get('mode')
if not mode:
return self.build_response(
request,
self.MODE_ENTER_FROM_LOCATION,
from_location_form=ExistingLocationWithBoxesForm(
prefix=self.FORM_PREFIX_FROM_LOCATION,
),
errors=["Missing mode parameter"],
status=HTTPStatus.BAD_REQUEST,
)
if mode == self.MODE_ENTER_FROM_LOCATION:
return self.post_from_location_form(request)
if mode == self.MODE_ENTER_TO_LOCATION:
return self.post_to_location_form(request)
if mode == self.MODE_CONFIRM_MERGE:
return self.post_confirm_merge_form(request)
return error_page(
request,
f"Unrecognized mode {mode} in ManualPalletMoveView"
)
[docs] def post_from_location_form(self, request):
from_location_form = ExistingLocationWithBoxesForm(
request.POST,
prefix=self.FORM_PREFIX_FROM_LOCATION,
)
if not from_location_form.is_valid():
return self.build_response(
request,
self.MODE_ENTER_FROM_LOCATION,
from_location_form=from_location_form,
status=HTTPStatus.BAD_REQUEST,
)
from_location = from_location_form.cleaned_data.get('location')
return self.show_to_location_form(
request,
from_location,
)
[docs] def post_to_location_form(self, request):
to_location_form = MoveToLocationForm(
request.POST,
prefix=self.FORM_PREFIX_TO_LOCATION,
)
if not to_location_form.is_valid():
return self.build_response(
request,
self.MODE_ENTER_TO_LOCATION,
to_location_form=to_location_form,
status=HTTPStatus.BAD_REQUEST,
)
from_location = to_location_form.cleaned_data['from_location']
to_location = to_location_form.cleaned_data['location']
boxes_at_to_location = \
Box.objects.filter(location=to_location).count()
if boxes_at_to_location == 0:
return self.move_boxes(request, from_location, to_location)
return self.build_response(
request,
self.MODE_CONFIRM_MERGE,
confirm_merge_form=ConfirmMergeForm(
prefix=self.FORM_PREFIX_CONFIRM_MERGE,
initial={
'from_location': from_location,
'to_location': to_location,
'boxes_at_to_location': boxes_at_to_location,
},
),
)
[docs] def post_confirm_merge_form(self, request):
confirm_merge_form = ConfirmMergeForm(
request.POST,
prefix=self.FORM_PREFIX_CONFIRM_MERGE,
)
if not confirm_merge_form.is_valid():
return self.build_response(
request,
self.MODE_CONFIRM_MERGE,
confirm_merge_form=confirm_merge_form,
status=HTTPStatus.BAD_REQUEST,
)
from_location = \
confirm_merge_form.cleaned_data['from_location']
to_location = confirm_merge_form.cleaned_data['to_location']
action = confirm_merge_form.cleaned_data['action']
if action == ConfirmMergeForm.ACTION_CHANGE_LOCATION:
# this method sets mode appropriately
return self.show_to_location_form(
request,
from_location,
)
return self.move_boxes(request, from_location, to_location)
[docs] @staticmethod
def get_next_temp_name():
numbers = Pallet.objects.filter(
name__startswith='temp'
).annotate(
number=Substr('name', 5),
).values_list(
'number',
flat=True,
)
if not numbers:
return 'temp1'
max_number = 0
for n in numbers:
try:
n = int(n)
except (TypeError, ValueError):
continue
if n > max_number:
max_number = n
return f"temp{max_number + 1}"
[docs] def move_boxes(self, request, from_location, to_location):
pallet, box_count = self.get_pallet_and_box_count(
from_location,
to_location
)
box_manager = BoxManagementClass()
box_manager.pallet_finish(pallet)
return self.build_response(
request,
self.MODE_COMPLETE,
boxes_moved=box_count,
to_location=to_location,
)
[docs] @staticmethod
def get_pallet_and_box_count(from_location, to_location):
# Create temporary Pallet and PalletBox records in order to use
# pallet_finish
with transaction.atomic():
pallet = Pallet.objects.create(
name=ManualPalletMoveView.get_next_temp_name(),
location=to_location,
pallet_status=Pallet.MOVE,
)
boxes_to_move = Box.objects.filter(location=from_location)
pallet_boxes = []
for box in boxes_to_move:
pallet_box = PalletBox(
pallet=pallet,
box=box,
product=box.product,
exp_year=box.exp_year,
exp_month_start=box.exp_month_start,
exp_month_end=box.exp_month_end,
)
pallet_boxes.append(pallet_box)
PalletBox.objects.bulk_create(pallet_boxes)
return pallet, len(pallet_boxes)
[docs] def build_response(
self,
request,
mode,
from_location_form=None,
to_location_form=None,
confirm_merge_form=None,
boxes_moved=0,
to_location=None,
errors=None,
status=HTTPStatus.OK):
context = dict()
context['mode'] = mode
context['view_class'] = self.__class__
context['from_location_form'] = from_location_form
context['to_location_form'] = to_location_form
context['confirm_merge_form'] = confirm_merge_form
context['boxes_moved'] = boxes_moved
context['to_location'] = to_location
context['errors'] = errors or []
# context = {
# 'mode': mode,
# 'view_class': self.__class__,
# 'from_location_form': from_location_form,
# 'to_location_form': to_location_form,
# 'confirm_merge_form': confirm_merge_form,
# 'boxes_moved': boxes_moved,
# 'to_location': to_location,
# 'errors': error_list,
# },
# add user info
context = add_navbar_vars(self.request.user, context)
return render(
request,
self.template,
context,
status=status,
)
[docs]class ActivityDownloadView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.view_activity',
)
date_format = '%m/%d/%Y'
[docs] class Echo:
"""An object that implements just the write method of the file-like
interface.
"""
[docs] @staticmethod
def write(value):
"""
Write the value by returning it, instead of storing in a buffer.
:param value:
:return:
"""
return value
[docs] def write_rows(self):
yield [
'Box Number', 'Box Type',
'Row', 'Bin', 'Tier',
'Product', 'Product Category',
'Date Filled', 'Date Consumed',
'Exp Year', 'Exp Month Start', 'Exp Month End',
'Quantity', 'Duration', 'Adjustment Code',
]
for activity in Activity.objects.all():
date_filled = activity.date_filled
if date_filled:
date_filled = date_filled.strftime(self.date_format)
else:
date_filled = ''
date_consumed = activity.date_consumed
if date_consumed:
date_consumed = date_consumed.strftime(self.date_format)
else:
date_consumed = ''
row = [
activity.box_number,
activity.box_type,
activity.loc_row,
activity.loc_bin,
activity.loc_tier,
activity.prod_name,
activity.prod_cat_name,
date_filled,
date_consumed,
activity.exp_year,
activity.exp_month_start,
activity.exp_month_end,
activity.quantity,
activity.duration,
activity.adjustment_code,
]
yield row
[docs] def get(self, request, *args, **kwargs):
pseudo_buffer = self.Echo()
writer = csv_writer(pseudo_buffer)
response = StreamingHttpResponse(
(writer.writerow(row) for row in self.write_rows()),
content_type="text/csv"
)
response[
'Content-Disposition'] = 'attachment; filename="activities.csv"'
return response
[docs]class ManualBoxStatusView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.view_box',
)
template_name = 'fpiweb/manual_box_status.html'
MODE_ENTER_BOX_NUMBER = 'enter_box_number'
MODE_CONFIRMATION = 'confirmation'
[docs] @staticmethod
def build_context(
*,
mode,
box_number_form=None,
box=None,
box_type=None,
product_form=None,
product=None,
location_form=None,
location=None,
errors=None):
return {
'mode': mode,
'box_number_form': box_number_form,
'box': box,
'box_type': box_type,
'product_form': product_form,
'product': product,
'location_form': location_form,
'location': location,
'view_class': ManualBoxStatusView,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
"""
Prepare to display consume box number form for the first time.
:param request:
:param args:
:param kwargs:
:return:
"""
get_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=FilledBoxNumberForm(),
)
get_context = add_navbar_vars(request.user, get_context)
return render(request, self.template_name, get_context)
[docs] def post_box_number(self, request):
box_number_form = ExtantBoxNumberForm(request.POST)
if not box_number_form.is_valid():
box_number_failed_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=box_number_form,
errors=box_number_form.errors,
)
box_number_failed_context = add_navbar_vars(
request.user,
box_number_failed_context
)
return render(
request,
self.template_name,
box_number_failed_context,
status=HTTPStatus.NOT_FOUND,
)
box_number = box_number_form.cleaned_data.get('box_number')
# go get the final box info
box = Box.objects.select_related(
'box_type',
'product',
'location',
).get(box_number=box_number)
box_type = box.box_type
product = box.product
location = box.location
# go get the final box info after any modifications
post_context = self.build_context(
mode=self.MODE_CONFIRMATION,
box=box,
box_type=box_type,
product=product,
location=location,
)
post_context = add_navbar_vars(request.user, post_context)
return render(
request,
self.template_name,
post_context,
)
[docs] def post(self, request, *args, **kwargs):
mode = request.POST.get('mode')
if mode == self.MODE_ENTER_BOX_NUMBER:
return self.post_box_number(request)
logger.debug(f"Unrecognized mode '{mode}'")
context = dict()
context = add_navbar_vars(self.request.user, context)
return render(request, self.template_name, context)
[docs]class ManualNewBoxView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.add_box',
)
template_name = 'fpiweb/manual_new_box.html'
MODE_ENTER_BOX_NUMBER = 'enter_box_number'
MODE_CONFIRMATION = 'confirmation'
[docs] @staticmethod
def build_context(
*,
mode,
box_number_form=None,
box=None,
box_type=None,
box_type_form=None,
errors: Optional[list]=None):
return {
'mode': mode,
'box_number_form': box_number_form,
'box': box,
'box_type': box_type,
'box_type_form': box_type_form,
'view_class': ManualNewBoxView,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
"""
Prepare to display add new box number form for the first time.
:param request:
:param args:
:param kwargs:
:return:
"""
get_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=NewBoxNumberForm(),
box_type_form=ExistingBoxTypeForm(),
)
# add user info
get_context = add_navbar_vars(self.request.user, get_context)
return render(request, self.template_name, get_context)
[docs] def post_box_number(self, request):
box_number_form = NewBoxNumberForm(request.POST)
box_type_form = ExistingBoxTypeForm(request.POST)
if not box_number_form.is_valid():
box_number = box_number_form.data['box_number']
box_number_failed_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=box_number_form,
box_type_form=box_type_form,
errors=[f"Box {box_number} already in inventory"],
)
box_number_failed_context = add_navbar_vars(
self.request.user,
box_number_failed_context
)
return render(
request,
self.template_name,
box_number_failed_context,
status=HTTPStatus.NOT_FOUND,
)
box_number = box_number_form.cleaned_data.get('box_number')
if not box_type_form.is_valid():
box_type_failed_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=box_number_form,
box_type_form=box_type_form,
)
box_type_failed_context = add_navbar_vars(
self.request.user,
box_type_failed_context
)
return render(
request,
self.template_name,
box_type_failed_context,
status=HTTPStatus.NOT_FOUND,
)
box_type = box_type_form.cleaned_data['box_type']
# add the box to inventory
box_mgmt = BoxManagementClass()
_ = box_mgmt.box_new(box_number=box_number, box_type=box_type)
# present the final box info
context = self.build_context(
mode=self.MODE_CONFIRMATION,
box_number_form=box_number_form,
box=box_number,
box_type=box_type,
box_type_form=box_type_form,
)
context = add_navbar_vars(self.request.user, context)
return render(
request,
self.template_name,
context,
)
[docs] def post(self, request, *args, **kwargs):
mode = request.POST.get('mode')
if mode == self.MODE_ENTER_BOX_NUMBER:
return self.post_box_number(request)
logger.debug(f"Unrecognized mode '{mode}'")
context = dict()
context = add_navbar_vars(self.request.user, context)
return render(request, self.template_name, context)
[docs]class ManualCheckinBoxView(PermissionRequiredMixin, View):
""" Manually check a box into inventory. """
permission_required = (
'fpiweb.check_in_box',
)
template_name = 'fpiweb/manual_check_in_box.html'
MODE_ENTER_BOX_INFO = 'enter_box_info'
MODE_CONFIRMATION = 'confirmation'
[docs] @staticmethod
def build_context(
*,
mode,
box_number_form=None,
box_number=None,
box=None,
product_form=None,
product=None,
location_form=None,
location=None,
exp_year_form=None,
exp_year=None,
exp_month_start_form=None,
exp_month_start=None,
exp_month_end_form=None,
exp_month_end=None,
errors: Optional[list]=None):
return {
'mode': mode,
'box_number_form': box_number_form,
'box_number': box_number,
'box': box,
'product_form': product_form,
'product': product,
'location_form': location_form,
'location': location,
'exp_year_form': exp_year_form,
'exp_year': exp_year,
'exp_month_start_form': exp_month_start_form,
'exp_month_start': exp_month_start,
'exp_month_end_form': exp_month_end_form,
'exp_month_end': exp_month_end,
'view_class': ManualCheckinBoxView,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
get_context = self.build_context(
mode=self.MODE_ENTER_BOX_INFO,
box_number_form=EmptyBoxNumberForm(),
product_form=ExistingProductForm(),
location_form=ExistingLocationForm(),
exp_year_form=ExpYearForm(),
exp_month_start_form=ExpMoStartForm(),
exp_month_end_form=ExpMoEndForm(),
)
# add user info
get_context = add_navbar_vars(self.request.user, get_context)
return render(request, self.template_name, get_context)
[docs] def post_box_info(self, request):
"""
Validate the posted information.
:param request: container of initial or latest post
:return:
"""
# initialize variables that will be filled in later
box_number = None
box = None
product = None
location = None
exp_year = None
exp_month_start = None
exp_month_end = None
# start by hoping everything is ok -- then validate
status = HTTPStatus.OK
error_msgs = list()
# validate box number
box_number_form = ExtantBoxNumberForm(request.POST)
if not box_number_form.is_valid():
status = HTTPStatus.NOT_FOUND
error_msgs.append(f'Invalid box number')
else:
box_number = box_number_form.cleaned_data.get('box_number')
box = Box.objects.get(box_number=box_number)
# Validate product
product_form = ExistingProductForm(request.POST)
if not product_form.is_valid():
status = HTTPStatus.BAD_REQUEST
error_msgs.append('Missing product')
else:
product = product_form.cleaned_data.get('product')
# validate location
location_form = ExistingLocationForm(request.POST)
if not location_form.is_valid():
status = HTTPStatus.BAD_REQUEST
error_msgs.append('Missing location')
else:
location = location_form.cleaned_data.get('location')
# validate expiration year
exp_year_form = ExpYearForm(request.POST)
if not exp_year_form.is_valid():
status = HTTPStatus.BAD_REQUEST
error_msgs.append('invalid expiration year')
exp_year = exp_year_form.cleaned_data.get('exp_year')
# validate expiration months
exp_month_start_form = ExpMoStartForm(request.POST)
exp_month_end_form = ExpMoEndForm(request.POST)
if (not exp_month_start_form.is_valid()) or \
(not exp_month_end_form.is_valid()):
status = HTTPStatus.BAD_REQUEST
error_msgs.append('invalid expiration month start')
else:
exp_month_start = exp_month_start_form.cleaned_data.get(
'exp_month_start')
exp_month_end = exp_month_end_form.cleaned_data.get(
'exp_month_end')
validation = validation_exp_months_bool(
exp_month_start,
exp_month_end
)
if not validation.is_valid:
status = HTTPStatus.BAD_REQUEST
error_msgs = error_msgs + validation.error_msg_list
# Was everything valid? If not, report it
if status != HTTPStatus.OK:
validation_failed_context = self.build_context(
mode=self.MODE_ENTER_BOX_INFO,
box_number_form=box_number_form,
product_form=product_form,
location_form=location_form,
exp_year_form=exp_year_form,
exp_month_start_form=exp_month_start_form,
exp_month_end_form=exp_month_end_form,
errors=error_msgs,
)
return render(
request,
self.template_name,
validation_failed_context,
status=status
)
# apply fill box to database
box_mgmt = BoxManagementClass()
try:
box = box_mgmt.box_fill(
box=box,
location=location,
product=product,
exp_year=exp_year,
exp_mo_start=exp_month_start,
exp_mo_end=exp_month_end,
)
except ProjectError as xcp:
modify_box_failed_context = self.build_context(
mode=self.MODE_ENTER_BOX_INFO,
box_number_form=box_number_form,
box=box,
product_form=product_form,
product=product,
location_form=location_form,
location=location,
# exp_month_start_form=exp_month_start_form,
# exp_month_start=exp_month_start,
# exp_month_end_form=exp_month_end_form,
# exp_month_end=exp_month_end,
errors=[xcp],
)
return render(
request,
self.template_name,
modify_box_failed_context,
status=HTTPStatus.BAD_REQUEST,
)
# go get box info for the final display
# box_form = BoxItem()
filled_box = Box.objects.select_related(
'box_type',
'product',
'location',
).get(id=box.id)
box_type = box.box_type
product = box.product
location = box.location
# prepare context
post_context = self.build_context(
mode=self.MODE_CONFIRMATION,
box=filled_box,
product=product,
location=location,
exp_year=exp_year,
exp_month_start=exp_month_start,
exp_month_end=exp_month_end,
errors=[],
)
# add user info
post_context = add_navbar_vars(self.request.user, post_context)
return render(
request,
self.template_name,
post_context
)
[docs] def post(self, request, *args, **kwargs):
mode = request.POST.get('mode')
if mode == self.MODE_ENTER_BOX_INFO:
return self.post_box_info(request)
logger.debug(f"Unrecognized mode '{mode}'")
context = dict()
context = add_navbar_vars(self.request.user, context)
return render(request, self.template_name, context)
[docs]class ManualConsumeBoxView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.check_out_box',
)
template_name = 'fpiweb/manual_check_out_box.html'
MODE_ENTER_BOX_NUMBER = 'enter_box_number'
MODE_CONSUME_BOX = 'consume_box'
MODE_CONFIRMATION = 'confirmation'
[docs] @staticmethod
def build_context(
*,
mode,
box_number_form=None,
box=None,
box_type=None,
product_form=None,
product=None,
location_form=None,
location=None,
errors: Optional[list]=None):
return {
'mode': mode,
'box_number_form': box_number_form,
'box': box,
'box_type': box_type,
'product_form': product_form,
'product': product,
'location_form': location_form,
'location': location,
'view_class': ManualConsumeBoxView,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
"""
Prepare to display consume box number form for the first time.
:param request:
:param args:
:param kwargs:
:return:
"""
get_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=FilledBoxNumberForm,
)
context = add_navbar_vars(self.request.user, get_context)
return render(request, self.template_name, get_context)
[docs] def post_box_number(self, request):
box_number_form = FilledBoxNumberForm(request.POST)
if not box_number_form.is_valid():
box_number_failed_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=box_number_form,
errors=['Box number missing or box is empty']
)
box_number_failed_context = add_navbar_vars(
self.request.user,
box_number_failed_context
)
return render(
request,
self.template_name,
box_number_failed_context,
status=HTTPStatus.BAD_REQUEST,
)
box_number = box_number_form.cleaned_data.get('box_number')
# go get the final box info
filled_box = Box.objects.select_related(
'box_type',
'product',
'location',
).get(box_number=box_number)
box_type = filled_box.box_type
product = filled_box.product
location = filled_box.location
pre_consume_context = self.build_context(
mode=self.MODE_CONSUME_BOX,
box=filled_box,
box_type=box_type,
product=product,
location=location,
)
# get user info
pre_consume_context = add_navbar_vars(
self.request.user,
pre_consume_context
)
return render(
request,
self.template_name,
pre_consume_context,
)
[docs] def post_consume_box(self, request):
box_pk = request.POST.get('box_pk')
if not box_pk:
box_failed_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=FilledBoxNumberForm(),
errors=['Missing box_pk']
),
box_failed_context = add_navbar_vars(
request.user,
box_failed_context,
)
return render(
request,
self.template_name,
box_failed_context,
status=HTTPStatus.BAD_REQUEST,
)
box = Box.objects.get(pk=box_pk)
# apply box consumption to database
box_mgmt = BoxManagementClass()
box = box_mgmt.box_consume(box=box)
# go get the final box info after any modifications
empty_box = Box.objects.select_related(
'box_type',
).get(id=box.id)
box_type = box.box_type
post_consume_context = self.build_context(
mode=self.MODE_CONFIRMATION,
box=empty_box,
box_type=box_type,
)
# get user info
post_consume_context = add_navbar_vars(
self.request.user,
post_consume_context
)
return render(
request,
self.template_name,
post_consume_context
)
[docs] def post(self, request, *args, **kwargs):
mode = request.POST.get('mode')
if mode == self.MODE_ENTER_BOX_NUMBER:
return self.post_box_number(request)
if mode == self.MODE_CONSUME_BOX:
return self.post_consume_box(request)
logger.debug(f"Unrecognized mode '{mode}'")
context = dict()
context = add_navbar_vars(self.request.user, context)
return render(request, self.template_name, context)
[docs]class ManualMoveBoxView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.move_box',
)
template_name = 'fpiweb/manual_move_box.html'
MODE_ENTER_BOX_NUMBER = 'enter_box_number'
MODE_ENTER_LOCATION = 'enter_location'
MODE_CONFIRMATION = 'confirmation'
[docs] @staticmethod
def build_context(
*,
mode,
box_number_form=None,
box=None,
location_form=None,
errors: Optional[list]=None):
return {
'mode': mode,
'box_number_form': box_number_form,
'box': box,
'location_form': location_form,
'view_class': ManualMoveBoxView,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
get_context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=FilledBoxNumberForm(),
)
# add user info
get_context = add_navbar_vars(self.request.user, get_context)
return render(request, self.template_name, get_context)
[docs] def post_box_number(self, request):
box_number_form = FilledBoxNumberForm(request.POST)
if not box_number_form.is_valid():
context = self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
box_number_form=box_number_form,
errors=['Box number invalid']
)
context = add_navbar_vars(self.request.user, context)
return render(
request,
self.template_name,
context,
status=HTTPStatus.NOT_FOUND,
)
box_number = box_number_form.cleaned_data.get('box_number')
boxes = Box.select_location(
Box.objects.filter(box_number=box_number)
)
# prepare context
context = self.build_context(
mode=self.MODE_ENTER_LOCATION,
box=boxes.first(),
location_form=ExistingLocationForm(),
)
# add user info
context = add_navbar_vars(self.request.user, context)
return render(
request,
self.template_name,
context
)
[docs] def post_location(self, request):
box_pk = request.POST.get('box_pk')
if not box_pk:
return render(
request,
self.template_name,
self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
errors=['Missing box_pk']
),
status=HTTPStatus.BAD_REQUEST,
)
try:
box = Box.objects.get(pk=box_pk)
except Box.DoesNotExist as dne:
message = str(dne)
logger.error(message)
return render(
request,
self.template_name,
self.build_context(
mode=self.MODE_ENTER_BOX_NUMBER,
errors=[message]
),
status=HTTPStatus.NOT_FOUND,
)
location_form = ExistingLocationForm(request.POST)
if not location_form.is_valid():
context = self.build_context(
mode=self.MODE_ENTER_LOCATION,
box=box,
location_form=location_form,
errors=['invalid or missing location'],
),
context = add_navbar_vars(self.request.user, context)
return render(
request,
self.template_name,
context,
status=HTTPStatus.NOT_FOUND
)
location = location_form.cleaned_data.get('location')
if not location:
message = "Unable to retrieve location from form"
logger.error(message)
context = self.build_context(
mode=self.MODE_ENTER_LOCATION,
box=box,
location_form=location_form,
errors=['invalid or missing location'],
),
context = add_navbar_vars(self.request.user, context)
return render(
request,
self.template_name,
context,
status=HTTPStatus.NOT_FOUND
)
# apply location change to database
box_mgmt = BoxManagementClass()
box = box_mgmt.box_move(
box=box,
location=location
)
# prepare context for post location
post_context = self.build_context(
mode=self.MODE_CONFIRMATION,
box=box,
location_form=ExistingLocationForm(),
)
# add user info
post_context = add_navbar_vars(self.request.user, post_context)
return render(
request,
self.template_name,
post_context
)
[docs] def post(self, request, *args, **kwargs):
mode = request.POST.get('mode')
if mode == self.MODE_ENTER_BOX_NUMBER:
return self.post_box_number(request)
if mode == self.MODE_ENTER_LOCATION:
return self.post_location(request)
logger.debug(f"Unrecognized mode '{mode}'")
context = dict()
context = add_navbar_vars(self.request.user, context)
return render(request, self.template_name, context)
[docs]class PalletManagementView(PermissionRequiredMixin, View):
"""Select current pallet, add new pallet, delete pallet"""
permission_required = (
'fpiweb.dummy_profile',
)
template_name = 'fpiweb/pallet_management.html'
[docs] @staticmethod
def show_page(
request,
page_title='Pallet Management',
show_delete=True,
prompt=None,
pallet_select_form=None,
pallet_name_form=None,
status_code=HTTPStatus.OK):
"""
Prepare web page from the info received.
:param request: infor from Django
:param page_title: title to put in the browser tab
:param show_delete: should a delete option be displayee?
:param prompt: suggestion to the user about what to do
:param pallet_select_form: form to use to select an existing pallet
:param pallet_name_form: form to use to name a new pallet
:param status_code: status code to send to the browser
:return: a fully rendered web page to send to the browser
"""
context = {
'page_title': page_title,
'show_delete': show_delete,
'prompt': prompt,
'pallet_select_form': pallet_select_form or PalletSelectForm(),
'pallet_name_form': pallet_name_form or PalletNameForm(),
}
# add user info for navbar
context = add_navbar_vars(request.user, context)
return render(
request,
PalletManagementView.template_name,
context,
status=status_code
)
[docs] def get(self, request):
return self.show_page(request)
[docs]class PalletSelectView(PermissionRequiredMixin, FormView):
permission_required = (
'fpiweb.dummy_profile',
)
template_name = 'fpiweb/pallet_select.html'
success_url = reverse_lazy('fpiweb:index')
form_class = PalletSelectForm
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class UserManagementView(PermissionRequiredMixin, View):
"""
Allow staff and administrators to manage any users access
"""
permission_required = (
'fpiweb.view_system_maintenance',
)
template_name = 'fpiweb/user_management.html'
def __init__(self):
_ = super().__init__()
self.pm = ManageUserPermissions()
return
[docs] @staticmethod
def build_context(
*,
mode,
this_user_info=None,
target_user_info=None,
perm_level=None,
user_info_list=None,
user_list_count=None,
errors=None):
return {
'mode': mode,
'all_modes': MODES,
'view_class': UserManagementView,
'this_user_info': this_user_info,
'target_user_info': target_user_info,
'user_list_count': user_list_count,
'perm_level': perm_level,
'user_info_list': user_info_list,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
"""
Prepare to display list of users to change.
:param request:
:param args:
:param kwargs:
:return:
"""
# get permission level and other info about current user
this_user = request.user
this_user_info: UserInfo = self.pm.get_user_info(user_id=this_user.id)
# get all users to build user list
user_info_list = list()
user_model = get_user_model()
all_users = user_model.objects.all()
for user in all_users:
if user == this_user:
continue
user_info = self.pm.get_user_info(user.id)
if (user_info.highest_access_level <=
this_user_info.highest_access_level):
user_info_list.append(user_info)
user_info_list.sort(key=methodcaller('get_sort_key'))
# prepare context for template
get_context = self.build_context(
mode=MODES.MODE_SHOW_USERS,
this_user_info=this_user_info,
perm_level=this_user_info.highest_access_level,
user_info_list=user_info_list,
user_list_count=len(user_info_list)
)
get_context = add_navbar_vars(this_user, get_context)
return render(request, self.template_name, get_context)
[docs]class UserCreateview(PermissionRequiredMixin, CreateView):
"""
Create a new user to this system.
"""
permission_required = (
'fpiweb.view_system_maintenance',
# 'auth.view_user',
# 'auth.add_user',
# 'fpiweb.view_profile',
# 'fpiweb.add_profile',
)
model = settings.AUTH_USER_MODEL
template_name = 'fpiweb/user_edit.html'
success_url = reverse_lazy('fpiweb:user_management')
form_class = UserInfoForm
fields = [
'username',
'userpswd',
'confirm_pwd',
'force_password',
'first_name',
'last_name',
'email',
'title',
'access_level',
'is_active',
'is_superuser',
]
def __init__(self):
super().__init__()
self.pm = ManageUserPermissions()
# self.target_user_form = UserInfoForm()
return
[docs] @staticmethod
def build_add_context(
*,
mode,
key=None,
this_user_info=None,
target_user=None,
target_user_form=None,
errors=None):
return {
'mode': mode,
'key': key,
'all_modes': MODES,
'view_class': UserCreateview,
'this_user_info': this_user_info,
'target_user': target_user,
'target_user_form': target_user_form,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
"""
Prepare to display list of users to change.
:param request:
:param args:
:param kwargs:
:return:
"""
# get permission level and other info about current user
this_user = request.user
this_user_info: UserInfo = self.pm.get_user_info(user_id=this_user.id)
target_user = TargetUser()
# target_user_form = UserInfoForm()
# prepare context for template
get_context: dict = self.build_add_context(
mode=MODES.MODE_ADD_USER,
this_user_info=this_user_info,
target_user=target_user,
target_user_form=UserInfoForm(),
)
get_context = add_navbar_vars(self.request.user, get_context)
return render(request, self.template_name, get_context)
[docs] def post(self, request, *args, **kwargs):
"""
Validate the new user info.
:param request:
:param args:
:param kwargs:
:return:
"""
this_user = request.user
this_user_info: UserInfo = self.pm.get_user_info(user_id=this_user.id)
target_user_form = UserInfoForm(request.POST)
# convert access level text back to an enum
target_user_form.access_level = \
AccessDict[request.POST['access_level']].access_level
if not target_user_form.is_valid():
error_context: dict = self.build_add_context(
mode=MODES.MODE_ADD_USER,
this_user_info=this_user_info,
target_user_form=target_user_form,
errors=target_user_form.errors,
)
error_context = add_navbar_vars(self.request.user, error_context)
return render(request=request, template_name=self.template_name,
context=error_context)
else:
cleaned_data = target_user_form.cleaned_data
target_user = TargetUser(
username=cleaned_data['username'],
force_password=cleaned_data['force_password'],
first_name=cleaned_data['first_name'],
last_name=cleaned_data['last_name'],
email=cleaned_data['email'],
title=cleaned_data['title'],
access_level=cleaned_data['access_level'],
is_active=cleaned_data['is_active'],
)
try:
valid_user = self.pm.add_a_user(target_user)
except ValidationError as exc:
errors = {None, str(exc)}
error_context: dict = self.build_add_context(
mode=MODES.MODE_ADD_USER,
this_user_info=this_user_info,
target_user_form=target_user_form,
errors=errors,
)
error_context = add_navbar_vars(self.request.user,
error_context)
return render(
request=request,
template_name=self.template_name,
context=error_context
)
post_context: dict = self.build_add_context(
mode=MODES.MODE_CONFIRM,
this_user_info=this_user_info,
target_user=target_user,
)
post_context = add_navbar_vars(self.request.user, post_context)
return render(request, self.template_name, post_context)
[docs]class UserUpdateView(PermissionRequiredMixin, View):
"""
Update a user by someone else.
"""
permission_required = (
'fpiweb.view_system_maintenance',
# 'auth.view_user',
# 'auth.change_user',
# 'fpiweb.view_profile',
# 'fpiweb.change_profile'
)
form_class = UserInfoForm
template_name = 'fpiweb/user_edit.html'
success_url = reverse_lazy('fpiweb:user_management')
def __init__(self):
super().__init__()
self.pm = ManageUserPermissions()
return
[docs] @staticmethod
def build_update_context(
*,
mode,
key=None,
this_user_info=None,
target_user=None,
target_user_form=None,
errors=None):
return {
'mode': mode,
'key': key,
'all_modes': MODES,
'view_class': UserCreateview,
'this_user_info': this_user_info,
'target_user': target_user,
'target_user_form': target_user_form,
'errors': errors,
}
[docs] def get(self, request, *args, **kwargs):
"""
Modify the context before rendering the template.
:param request:
:param args:
:param kwargs:
:return:
"""
# get permission level and other info about current user
this_user = request.user
this_user_info: UserInfo = self.pm.get_user_info(user_id=this_user.id)
target_user_id = kwargs['pk']
# target_user = get_user_model().objects.select_related(
# 'profile').get(id=target_user_id)
target_user_info: UserInfo = \
self.pm.get_user_info(user_id=target_user_id)
target_user = TargetUser(
username=target_user_info.user.username,
# userpswd='',
# confirm_pwd='',
force_password=False,
first_name=target_user_info.user.first_name,
last_name=target_user_info.user.last_name,
email=target_user_info.user.email,
title=target_user_info.profile.title,
access_level=target_user_info.highest_access_level,
is_active=target_user_info.user.is_active,
# is_staff=target_user_info.user.is_staff,
# is_superuser=target_user_info.user.is_superuser,
)
target_user_form = UserInfoForm()
target_user_form.initial['username'] = target_user_info.user.username
target_user_form.initial['force_password'] = False,
target_user_form.initial[
'first_name'] = target_user_info.user.first_name
target_user_form.initial['last_name'] = target_user_info.user.last_name
target_user_form.initial['email'] = target_user_info.user.email
target_user_form.initial['title'] = target_user_info.profile.title
target_user_form.initial['access_level'] = \
target_user_info.highest_access_level.name
target_user_form.initial['is_active'] = target_user_info.user.is_active
# target_user_form.initial['is_staff'] = target_user_info.user.is_staff
# target_user_form.initial['is_superuser'] = \
# target_user_info.user.is_superuser
# prepare context for template
get_context: dict = self.build_update_context(
mode=MODES.MODE_UPDATE_USER,
key=target_user_id,
this_user_info=this_user_info,
target_user=target_user,
target_user_form=target_user_form,
)
get_context = add_navbar_vars(request.user, get_context)
return render(request, self.template_name, get_context)
[docs] def post(self, request, *args, **kwargs):
"""
Validate the new user info.
:param request:
:param args:
:param kwargs:
:return:
"""
this_user = request.user
this_user_info: UserInfo = self.pm.get_user_info(user_id=this_user.id)
key = request.POST['key']
target_user_form = UserInfoForm(request.POST)
# convert access level text back to an enum
target_user_form.access_level = \
AccessDict[request.POST['access_level']].access_level
if not target_user_form.is_valid():
error_context: dict = self.build_update_context(
mode=MODES.MODE_UPDATE_USER,
key=key,
this_user_info=this_user_info,
target_user_form=target_user_form,
errors=target_user_form.errors,
)
error_context = add_navbar_vars(self.request.user, error_context)
return render(request=request, template_name=self.template_name,
context=error_context)
else:
cleaned_data = target_user_form.cleaned_data
target_user = TargetUser(
username=cleaned_data['username'],
force_password=cleaned_data['force_password'],
first_name=cleaned_data['first_name'],
last_name=cleaned_data['last_name'],
email=cleaned_data['email'],
title=cleaned_data['title'],
access_level=cleaned_data['access_level'],
is_active=cleaned_data['is_active'],
)
self.pm.change_a_user(userid=key, target_user=target_user)
post_context: dict = self.build_update_context(
mode=MODES.MODE_CONFIRM,
this_user_info=this_user_info,
target_user=target_user,
)
post_context = add_navbar_vars(self.request.user, post_context)
return render(request, self.template_name, post_context)
[docs]class ProductCategoryCreateView(PermissionRequiredMixin, CreateView):
"""
Create a Product Category using a generic CreateView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.add_product_category',
)
model = ProductCategory
template_name = 'fpiweb/product_category_edit.html'
context_object_name = 'product_category'
success_url = reverse_lazy('fpiweb:product_category_view')
form_class = ProductCategoryForm
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductCategoryListView(PermissionRequiredMixin, ListView):
"""
List of existing Product Categories using a generic ListView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.view_product_category',
)
model = ProductCategory
template_name = 'fpiweb/product_category_list.html'
context_object_name = 'product_category_list_content'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductCategoryUpdateView(PermissionRequiredMixin, UpdateView):
"""
Update a Product Category using a generic UpdateView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.change_product_category',
)
model = ProductCategory
template_name = 'fpiweb/product_category_edit.html'
context_object_name = 'product_category'
form_class = ProductCategoryForm
success_url = reverse_lazy('fpiweb:product_category_view')
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductNameCreateView(PermissionRequiredMixin, CreateView):
"""
Create a Product using a generic CreateView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.add_product_name',
)
model = Product
template_name = 'fpiweb/product_name_edit.html'
context_object_name = 'product_name'
success_url = reverse_lazy('fpiweb:product_name_view')
form_class = ProductNameForm
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductNameListView(PermissionRequiredMixin, ListView):
"""
List of Products using a generic ListView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.view_product_name',
)
model = Product
template_name = 'fpiweb/product_name_list.html'
context_object_name = 'product_name_list_content'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductNameUpdateView(PermissionRequiredMixin, UpdateView):
"""
Update a Product using a generic UpdateView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.change_product_name',
)
model = Product
template_name = 'fpiweb/product_name_edit.html'
context_object_name = 'product_name'
form_class = ProductNameForm
success_url = reverse_lazy('fpiweb:product_name_view')
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductExampleListView(PermissionRequiredMixin, ListView):
"""
List of existing Product Examples using a generic ListView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.view_product_example',
)
model = ProductExample
template_name = 'fpiweb/product_example_list.html'
context_object_name = 'product_example_list_content'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductExampleCreateView(PermissionRequiredMixin,
SuccessMessageMixin,
CreateView):
"""
Create a Product Example using a generic CreateView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.add_product_example',
)
model = ProductExample
template_name = 'fpiweb/product_example_edit.html'
context_object_name = 'product_example'
success_url = reverse_lazy('fpiweb:product_example_view')
success_message = 'A new Product Example has been successfully added.'
form_class = ProductExampleForm
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductExampleUpdateView(PermissionRequiredMixin,
SuccessMessageMixin,
UpdateView):
"""
Update a Product Example using a generic UpdateView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.change_product_example_name',
)
model = ProductExample
template_name = 'fpiweb/product_example_edit.html'
context_object_name = 'product_example'
form_class = ProductExampleForm
success_url = reverse_lazy('fpiweb:product_example_view')
success_message = "The Product Example has been has been successfully " \
"updated."
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ProductExampleDeleteView(PermissionRequiredMixin, DeleteView):
"""
Delete a Product Example using a generic DeleteView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.delete_product_example_name',
)
model = ProductExample
template_name = 'fpiweb/product_example_delete.html'
context_object_name = 'product_example'
success_url = reverse_lazy('fpiweb:product_example_view')
success_message ='The Product Example has been successfully deleted.'
form_class = ProductExampleForm
[docs] def get_context_data(self, **kwargs):
"""
Modify the context before rendering the template.
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
# context['action'] = reverse('fpiweb:product_example_delete',
# kwargs={'pk': self.get_object().id})
context = add_navbar_vars(self.request.user, context)
return context
# delete the Procduct Example and show the Success Message
[docs] def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
self.object.delete()
messages.add_message(request, messages.SUCCESS,
self.success_message)
return HttpResponseRedirect(success_url)
[docs]class ManualLocTableListView(PermissionRequiredMixin, ListView):
"""
List the location table entries so they can be edited as needed.
Added by Mike Rehner
"""
permission_required = (
'fpiweb.view_manual_loc_table',
)
model = Location
template_name = 'fpiweb/manual_loc_table_list.html'
context_object_name = 'manual_loc_table'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ManualLocTableCreateView(PermissionRequiredMixin, CreateView):
"""
Create a new entry for Location Table using a generic CreateView.
Added by Mike Rehner
"""
permission_required = (
'fpiweb.add_manual_location_table',
)
model = Location
template_name = 'fpiweb/manual_loc_table_edit.html'
context_object_name = 'manual_loc_table'
success_url = reverse_lazy('fpiweb:manual_loc_table_view')
form_class = ManualLocTableForm
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class ManualLocTableUpdateView(PermissionRequiredMixin, UpdateView):
"""
Update a Rebuild Location Table using a generic UpdateView.
"""
# by Mike Rehner adding permission but not sure how its granted
permission_required = (
'fpiweb.change_manual_loc_table',
)
model = Location
template_name = 'fpiweb/manual_loc_table_edit.html'
context_object_name = 'manual_loc_table'
form_class = ManualLocTableForm
success_url = reverse_lazy('fpiweb:manual_loc_table_view')
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class RebuildLocTableStartView(PermissionRequiredMixin, View):
permission_required = (
'fpiweb.view_constraints',
'fpiweb.view_location'
'fpiweb.add_location'
)
model = Location
template_name = 'fpiweb/rebuild_loc_table_start.html'
context_object_name = 'rebuild_loc_table'
# success_url = reverse_lazy('fpiweb:rebuild_loc_table_start_view')
[docs] def get(self, request):
row_constraint = Constraints.objects.get(
constraint_name=Constraints.ROW
)
if row_constraint.constraint_type == Constraints.INT_RANGE:
row_min = row_constraint.constraint_min
row_max = row_constraint.constraint_max
else:
row_min = 'Error in row constraint_min'
row_max = 'Error in row constraint_max'
bin_constraint = Constraints.objects.get(
constraint_name=Constraints.BIN
)
if bin_constraint.constraint_type == Constraints.INT_RANGE:
bin_min = bin_constraint.constraint_min
bin_max = bin_constraint.constraint_max
else:
bin_min = 'Error in Bin constraint_min'
bin_max = 'Error in Bin constraint_max'
tier_constraint = Constraints.objects.get(
constraint_name=Constraints.TIER
)
if tier_constraint.constraint_type == Constraints.CHAR_LIST:
tiers = tier_constraint.constraint_list
else:
tiers = 'Error in Tier constraint_list'
exclusions_constraint = Constraints.objects.get(
constraint_name=Constraints.LOCATION_EXCLUSIONS)
if exclusions_constraint.constraint_type == Constraints.CHAR_LIST:
exclusion_str = exclusions_constraint.constraint_list
else:
exclusion_str = 'Error in Location Exclusions constraint_list'
context = {'row_max': row_max, 'row_min': row_min,
'bin_min': bin_min, 'bin_max': bin_max,
'tiers': tiers, 'exclusion_str': exclusion_str, }
context = add_navbar_vars(request.user, context)
return render(request, "fpiweb/rebuild_loc_table_start.html",
context, )
[docs]class RebuildLocTableFinishView(PermissionRequiredMixin, View):
# Mike Rehner not sure about these permissions
permission_required = (
'fpiweb.view_constraints',
'fpiweb.view_location'
'fpiweb.add_location'
)
model = Location
template_name = 'fpiweb/rebuild_loc_table_finish.html'
context_object_name = 'rebuild_loc_table'
# Updates LocRow, LocBin and LocTier tables with latest data from
# Constraints table
# Requires that exclusion data be in a comma separated string
# returns number of rows, bins and tiers added/updated
# @property
[docs] def update_row_bin_tier_tables(self) -> Tuple[int, int, int]:
row_nnn = 0
bin_nnn = 0
tier_nnn = 0
# Get Constraint object with constraint_name
# Constraints.Constraints.CONSTRAINT_NAME_CHOICES.ROW
row_constraint_record = Constraints.objects.get(
constraint_name=Constraints.ROW
)
# if type == int then use default INT_RANGE?
if row_constraint_record.constraint_type == Constraints.INT_RANGE:
row_min = int(row_constraint_record.constraint_min)
row_max = int(row_constraint_record.constraint_max)
for row_num in range(row_min, row_max + 1):
row_record, created = LocRow.objects.get_or_create(
loc_row=f'{row_num:02}')
# if new record or loc_row_descr is empty
if (created or row_record.loc_row_descr is None or
row_record.loc_row_descr == ''):
row_record.loc_row_descr = f'Row {row_num:02}'
row_record.save()
row_nnn = row_nnn + 1
# Get Constraint object with constraint_name
# Constraints.Constraints.CONSTRAINT_NAME_CHOICES.BIN
bin_constraint_record = Constraints.objects.get(
constraint_name=Constraints.BIN
)
# if type == int then use default INT_RANGE?
if bin_constraint_record.constraint_type == Constraints.INT_RANGE:
bin_min = int(bin_constraint_record.constraint_min)
bin_max = int(bin_constraint_record.constraint_max)
for bin_num in range(bin_min, bin_max + 1):
bin_record, created = LocBin.objects.get_or_create(
loc_bin=f'{bin_num:02}')
if (created or bin_record.loc_bin_descr is None or
bin_record.loc_bin_descr == ''):
bin_record.loc_bin_descr = f'Bin {bin_num:02}'
bin_record.save()
bin_nnn = bin_nnn + 1
tier_constraint_record = Constraints.objects.get(
constraint_name=Constraints.TIER
)
if tier_constraint_record.constraint_type == Constraints.CHAR_LIST:
tier_str = tier_constraint_record.constraint_list
# convert comma separated string to a list
tier_list = [entry.strip() for entry in tier_str.split(',')]
for tier_name in tier_list:
tier_record, created = LocTier.objects.get_or_create(
loc_tier=f'{tier_name}')
if (created or tier_record.loc_tier_descr is None or
tier_record.loc_tier_descr == ''):
tier_record.loc_tier_descr = f'Tier {tier_name}'
tier_record.save()
tier_nnn = tier_nnn + 1
return row_nnn, bin_nnn, tier_nnn
# returns a list of exclusion constraint keys
# requires that exclusion list items be comma separated in Constraint table
[docs] def build_exclusion_constraint_list(self):
exclusion_constraint_record = Constraints.objects.get(
constraint_name=Constraints.LOCATION_EXCLUSIONS)
if (exclusion_constraint_record.constraint_type ==
Constraints.CHAR_LIST):
exclusion_str = exclusion_constraint_record.constraint_list
# return exclusion_str as a list
return [entry.strip() for entry in exclusion_str.split(',')]
else:
return ['Error in views.RebuildLocTableFinishView'
'.build_exclusion_constraint_list']
# Adds new records to the Location table based on updated values from
# Constraints table.
# Requires Loc_Row, Loc_Bin and Loc Tier tables previously updated from
# Constraints table
# returns number of new records created in Location table
[docs] def rebuild_location_table(self) -> int:
loc_records_nnn = 0 # the number of location table records changed
# grab the location exclusion list into memory
exclusion_constraint_list = self.build_exclusion_constraint_list()
for row_record in LocRow.objects.all():
row_code_key = row_record.loc_row
for bin_record in LocBin.objects.all():
bin_code_key = bin_record.loc_bin
for tier_record in LocTier.objects.all():
tier_code_key = tier_record.loc_tier
combination_loc_key = (
row_code_key + bin_code_key + tier_code_key
)
if not Location.objects.filter(
loc_code=combination_loc_key):
if (combination_loc_key not in
exclusion_constraint_list):
r = Location(loc_code=combination_loc_key,
loc_descr=f'Row {row_code_key} '
f'Bin {bin_code_key} '
f'Tier {tier_code_key}',
loc_row=LocRow.objects.get(
loc_row=row_code_key),
loc_bin=LocBin.objects.get(
loc_bin=bin_code_key),
loc_tier=LocTier.objects.get(
loc_tier=tier_code_key))
r.save()
loc_records_nnn = loc_records_nnn + 1
return loc_records_nnn
[docs] def get(self, request):
# time.sleep(10) # Used to test spinner from previous page
rows_added, bins_added, tiers_added = self.update_row_bin_tier_tables()
location_records_added = self.rebuild_location_table()
context = {'location_records_added': location_records_added,
'rows_added': rows_added,
'bins_added': bins_added,
'tiers_added': tiers_added, }
context = add_navbar_vars(request.user, context)
return render(request, "fpiweb/rebuild_loc_table_finish.html", context)
# page shows a spinner while Location table is updated on server
[docs]class RebuildLocTableProgressView(PermissionRequiredMixin, View):
# Mike Rehner not sure about these permissions
permission_required = (
'fpiweb.view_constraints',
'fpiweb.view_location'
'fpiweb.add_location'
)
[docs] def get(self, request):
context = add_navbar_vars(request.user, dict())
return render(request, "fpiweb/rebuild_loc_table_progress.html",
context)
[docs]class BoxTypeMaintenanceListView(PermissionRequiredMixin, ListView):
# List of existing BoxTypes using a generic ListView
# by Mike Rehner adding permission but not sure how this works?
permission_required = ('fpiweb.view_box_type')
model = BoxType
template_name = 'fpiweb/box_type_maintenance_list.html'
context_object_name = 'box_type_maintenance_list_content'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BoxTypeMaintenanceCreateView(PermissionRequiredMixin,
SuccessMessageMixin,
CreateView):
# create a Box Type using a generic CreateView
# by Mike Rehner adding permission but not sure how this works?
permission_required = ('fpiweb.add_box_type_maintenance')
model = BoxType
template_name = 'fpiweb/box_type_maintenance_edit.html'
context_object_name = 'box_type_maintenance'
success_url = reverse_lazy('fpiweb:box_type_maintenance_view')
success_message = " A new BoxType has been successfully added."
form_class = BoxTypeMaintenanceForm
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BoxTypeMaintenanceUpdateView(PermissionRequiredMixin,
SuccessMessageMixin,
UpdateView):
# Update a Box Type using a generic update view
# by Mike Rehner adding permission but not sure how this works?
permission_required = ('fpiweb.change_box_type_maintenance')
model = BoxType
template_name = 'fpiweb/box_type_maintenance_edit.html'
context_object_name = 'box_type_maintenance'
form_class = BoxTypeMaintenanceForm
success_url = reverse_lazy('fpiweb:box_type_maintenance_view')
success_message = 'The Box Type has been successfully updated.'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs]class BoxTypeMaintenanceDeleteView(PermissionRequiredMixin, DeleteView):
# Delete a Box Type using a generic DeleteView
# added by Mike Rehner adding permission but not sure how this works?
permission_required = ('fpiweb.delete_box_type_maintenance')
model = BoxType
template_name = 'fpiweb/box_type_maintenance_delete.html'
context_object_name = 'box_type_maintenance'
success_url = reverse_lazy('fpiweb:box_type_maintenance_view')
form_class = BoxTypeMaintenanceForm
success_message = 'Box Type successfully deleted.'
error_message = 'Unable to Delete this Box Type. Before deleting this ' \
'Box Type it is necessary to Delete every Box ' \
'that uses this Box Type. (Cascade Delete Protected) ' \
'Click Cancel Button to return to Box Type List page.'
[docs] def get_context_data(self, **kwargs):
"""
Add info needed for the navigation bar
:param kwargs:
:return:
"""
context = super().get_context_data(**kwargs)
context = add_navbar_vars(self.request.user, context)
return context
[docs] def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
try:
# Return to BoxType List page and show success message
self.object.delete()
messages.add_message(request, messages.SUCCESS,
self.success_message )
return HttpResponseRedirect(success_url)
except ProtectedError:
# Return to BoxType List page and show cascade Protected Error
# message
messages.add_message(request, messages.ERROR, self.error_message)
return HttpResponseRedirect(self.request.path_info)
# class ManualNotification(LoginRequiredMixin, TemplateView):
# """
# Ask a question or notify the user of something.
# """
# template_name = 'fpiweb/manual_generic_notification.html'
#
# def get_context_data(self, **kwargs):
# """
# Get info from request and populate context from it.
#
# :param kwargs:
# :return:
# """
# context = super().get_context_data(**kwargs)
# request = context.get_request()
# title = request.
# EOF