Source code for fpiweb.views

"""
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] def show_forms_response( self, request, build_pallet_form, box_forms, pallet_form, status=HTTPStatus.BAD_REQUEST): """ Display page with BuildPalletForm and BoxItemForms :param request: :param build_pallet_form: :param box_forms: :param pallet_form: :param status: :return: """ context = { 'form': build_pallet_form, 'box_forms': box_forms, 'pallet_form': pallet_form, } context = add_navbar_vars(request.user, context) return render( request, self.form_template, context, status=status, )
[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] def process_build_pallet_forms(self, request): build_pallet_form = BuildPalletForm( request.POST, prefix=self.build_pallet_form_prefix ) box_forms = self.BoxFormFactory( request.POST, prefix=self.formset_prefix, ) pallet_form = HiddenPalletForm( request.POST, prefix=self.hidden_pallet_form_prefix, ) build_pallet_form_valid = build_pallet_form.is_valid() if not build_pallet_form_valid: logger.debug("BuildPalletForm not valid") box_forms_valid = box_forms.is_valid() if not box_forms_valid: logger.debug("BoxForms not valid") pallet_form_valid = pallet_form.is_valid() if not pallet_form_valid: logger.debug("HiddenPalletForm not valid") if not all([ build_pallet_form_valid, box_forms_valid, pallet_form_valid ]): return self.show_forms_response( request, build_pallet_form, box_forms, pallet_form, ) try: pallet, location, boxes_by_box_number = \ self.prepare_pallet_and_pallet_boxes( pallet_form, build_pallet_form, box_forms, ) except BuildPalletError: return self.show_forms_response( request, build_pallet_form, box_forms, pallet_form, ) box_management = BoxManagementClass() box_management.pallet_finish(pallet) context = { 'location': location, 'boxes': boxes_by_box_number.values(), } context = add_navbar_vars(request.user, context) return render( request, self.confirmation_template, context, )
[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 BoxItemFormView(PermissionRequiredMixin, View): permission_required = ( 'fpiweb.add_box', ) template_name = 'fpiweb/box_form.html'
[docs] @staticmethod def get_form(pallet_box, prefix=None): kwargs = { 'initial': BoxItemForm.get_initial_from_box(pallet_box) } if prefix: kwargs['prefix'] = prefix form = BoxItemForm(**kwargs) return form
[docs] def post(self, request): scan_data = request.POST.get('scanData') box_number = ScannerView.get_keyed_in_box_number( request.POST.get('boxNumber'), ) prefix = request.POST.get('prefix') pallet_pk = request.POST.get('palletPk') try: box, created = ScannerView.get_box(scan_data, box_number) except ScannerViewError as sve: error = str(sve) logger.error(error) return HttpResponse("Scan failed.", status=HTTPStatus.NOT_FOUND) try: pallet = Pallet.objects.get(pk=pallet_pk) except Pallet.DoesNotExist as dne: error = f"Pallet pk={pallet_pk} not found" logger.error(error) logger.error(dne) return HttpResponse(error, status=HTTPStatus.NOT_FOUND) # If box is filled, empty it before continuing # TODO Jun 15 2020 travis - remove this check so activity is # recorded properly if box.is_filled(): box_management = BoxManagementClass() box_management.box_consume(box) # Look for PalletBox record pallet_box, created = PalletBox.objects.get_or_create( box_number=box.box_number, box=box, pallet=pallet ) context = { 'box_number': box.box_number, 'form': self.get_form( pallet_box, prefix), } return render( request, self.template_name, context, )
[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 show_to_location_form(self, request, from_location): return self.build_response( request, self.MODE_ENTER_TO_LOCATION, to_location_form=MoveToLocationForm( prefix=self.FORM_PREFIX_TO_LOCATION, initial={ 'from_location': 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] def form_valid(self, form): # This method is called when valid form data has been POSTed. # It should return an HttpResponse. # form.send_email() pallet = form.cleaned_data['pallet'] # set default status pallet.pallet_status = Pallet.FILL user, profile = get_user_and_profile(self.request) profile.active_pallet = pallet profile.save() return super().form_valid(form)
[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