from django.shortcuts import render, redirect from django.urls import resolve, reverse from django.contrib.auth import logout, login from django.contrib.auth.models import User from django.http import HttpResponse from .navbar import NavBar from django.contrib.auth.forms import AuthenticationForm as AuthForm from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth import update_session_auth_hash from django.views import View import django.forms as forms from django.views.generic import TemplateView, DetailView from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from .models import Storage, Stock, Component, Distributor, Manufacturer, Package, ComponentParameter, ComponentParameterType, DistributorNum from .qr_parser import QrCodeValidator from django.core.paginator import Paginator from django.core.exceptions import ValidationError from django.db import IntegrityError from django.db.models import ProtectedError from .forms import * from django.db.models import Q from django.db.models.functions import Lower import uuid class QrSearchForm(forms.Form): my_qr_validator = QrCodeValidator() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) qr_search = forms.CharField(label='qr_search', validators=[my_qr_validator]) class BaseTemplateMixin(object): navbar_selected = '' base_title = '' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) base_context = { 'navbar': NavBar.get_navbar(self.navbar_selected, self.request.user), 'title': NavBar.get_brand()+' / '+ self.base_title, 'login_active': False, } context['base'] = base_context return context def post(self, request, *args, **kwargs): data = request.POST if 'qr_search' not in data: super().post(request, *args, **kwargs) print('QR',data['qr_search']) f = QrSearchForm(data) if f.is_valid(): return redirect(f.my_qr_validator.get_redirect_url(f.cleaned_data['qr_search'])) return self.get(request) class ChangePasswordView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): template_name = 'parts/change-pw.html' navbar_selected = 'Main' base_title = 'Change Password' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form'] = PasswordChangeForm(self.request.user) return context def post(self, request, *args, **kwargs): if 'submit-change-pw' not in request.POST: return super().post(request, *args, **kwargs) form = PasswordChangeForm(request.user, data=request.POST) if form.is_valid(): user = form.save() update_session_auth_hash(request, user) return redirect('parts-main') else: pass context = self.get_context_data(**kwargs) if form.errors: context['form'] = form return self.render_to_response(context) class MainView(BaseTemplateMixin, TemplateView): template_name = 'parts/main.html' navbar_selected = 'Main' base_title = 'Main' def logout_view(request): logout(request) return redirect('parts-main') def login_view(request): base_context = { 'navbar': NavBar.get_navbar('Login', request.user), 'title': NavBar.get_brand()+' / '+'Login', 'login_active': True, } if request.user.is_authenticated: next_param = request.GET.get('next') if next_param is not None: return redirect(next_param) else: return redirect('parts-main') if request.method == 'POST': form = AuthForm(data=request.POST) if form.is_valid(): valid_user = form.get_user() login(request, valid_user) next_param = request.GET.get('next') if next_param is not None: return redirect(next_param) else: return redirect('parts-main') else: form = AuthForm() context = { 'base': base_context, 'form': form, } return render(request, 'parts/login.html', context) # Create your views here. class ComponentView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): template_name = 'parts/components.html' base_title = 'Components' navbar_selected = 'Components' default_page_size = 25 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) comp_page_num = self.request.GET.get('comp_page', default=1) comp_paginator = Paginator(Component.objects.all(), self.default_page_size) context['components'] = comp_paginator.get_page(comp_page_num) context['comp_form'] = ComponentForm() return context def handle_new_component_post(self, request, **kwargs): cform = ComponentForm(data=request.POST, files=request.FILES) if cform.is_valid(): cform.save() context = self.get_context_data(**kwargs) if not cform.is_valid(): context['comp_form'] = cform return self.render_to_response(context) def post(self, request, *args, **kwargs): if 'submit-edit-component' in request.POST: return self.handle_new_component_post(request, **kwargs) else: return super().post(request, *args, **kwargs) class PackageView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): template_name = 'parts/packages.html' base_title = 'Packages' navbar_selected = 'Packages' default_page_size = 25 def search_packages(self, search): qs = Package.objects.all() if not search: return qs search_fragments = search.strip().split() for search in search_fragments: if search.lower() == 'smd': s_filter = Q(name__icontains = search) | Q(smd = True) else: try: pin_count = int(search) s_filter = Q(name__icontains = search) | Q(pin_count=pin_count) except: s_filter = Q(name__icontains = search) qs = qs.filter(s_filter) return qs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) page_num = self.request.GET.get('page', default=1) search_string = self.request.GET.get('search', default=None) package_queryset = self.search_packages(search_string) paginator = Paginator(package_queryset, self.default_page_size) context['search_string'] = search_string context['packages'] = paginator.get_page(page_num) context['new_pkg_form'] = PackageForm() return context def handle_add_new_package(self, request): form = PackageForm(data=request.POST, files=request.FILES) if form.is_valid(): form.save() context = self.get_context_data() if not form.is_valid(): context['new_pkg_form'] = form return self.render_to_response(context) def post(self, request, *args, **kwargs): if 'submit-pkg-add-new' in request.POST: return self.handle_add_new_package(request) return super().post(request, *args, **kwargs) class DistributorView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): template_name = 'parts/distributors.html' base_title = 'Distributors' navbar_selected = 'Distributors' default_page_size = 25 def search_distributors(self, search): qs = Distributor.objects.all() if not search: return qs search_fragments = search.strip().split() for search in search_fragments: qs = qs.filter(Q(name__icontains = search) | Q(website__icontains = search)) return qs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) page_num = self.request.GET.get('page', default=1) search_string = self.request.GET.get('search', default=None) queryset = self.search_distributors(search_string) paginator = Paginator(queryset, self.default_page_size) context['search_string'] = search_string context['distributors'] = paginator.get_page(page_num) context['new_distri_form'] = DistributorForm() return context def handle_add_new_distributor(self, request): form = DistributorForm(data=request.POST, files=request.FILES) if form.is_valid(): form.save() context = self.get_context_data() if not form.is_valid(): context['new_distri_form'] = form return self.render_to_response(context) def post(self, request, *args, **kwargs): if 'submit-distri-add-new' in request.POST: return self.handle_add_new_distributor(request) return super().post(request, *args, **kwargs) class StockView(LoginRequiredMixin, BaseTemplateMixin, TemplateView): template_name = 'parts/stocks.html' base_title = 'Stocks' navbar_selected = 'Stocks' default_pagination_size = 25 def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) storage_page = self.request.GET.get('storage_page') if storage_page is None: storage_page = 1 low_stock_page = self.request.GET.get('low_stock_page') if low_stock_page is None: low_stock_page = 1 storage_paginator = Paginator(Storage.objects.filter(parent_storage=None), self.default_pagination_size) low_stock_paginator = Paginator(Stock.get_under_watermark(), self.default_pagination_size) context['low_stocks'] = low_stock_paginator.get_page(low_stock_page) context['storages'] = storage_paginator.get_page(storage_page) add_stor_form = AddSubStorageForm() add_stor_form.fields['responsible'].initial = self.request.user.username context['add_storage_form'] = add_stor_form return context def handle_add_storage(self, request, **kwargs): return_invalid_form = False f = AddSubStorageForm(data=request.POST) if f.is_valid(): new_storage_name = f.cleaned_data['storage_name'] try: resp_user = User.objects.get(username=f.cleaned_data['responsible']) except Exception as _: resp_user = None f.add_error('responsible', 'Invalid Responsible User') return_invalid_form = True if resp_user is not None: try: Storage.objects.create(name=new_storage_name, responsible=resp_user, parent_storage=None) except ValidationError as verr: return_invalid_form = True f.add_error('storage_name', ' .'.join(verr.messages)) else: return_invalid_form = True context = self.get_context_data(**kwargs) if return_invalid_form: context['add_storage_form'] = f return self.render_to_response(context) def post(self, request, **kwargs): if 'submit-add-storage' in request.POST: return self.handle_add_storage(request, **kwargs) return super().post(request, **kwargs) class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView): template_name = 'parts/stocks-detail.html' model = Storage pk_url_kwarg = 'uuid' base_title = '' navbar_selected = 'Stocks' default_pagination_size = 8 def get_breadcrumbs(self): crumbs = self.object.get_path_components() # Reverse list and drop the last element of the reversed list crumbs = crumbs[::-1][:-1] return crumbs def search_stock_queryset(self, search): stocks_in_storage = Stock.objects.filter(storage=self.object).order_by(Lower('component__name')) if search is None or search == '': return stocks_in_storage if search.startswith('[comp_uuid]'): search = search.replace('[comp_uuid]', '') # Check if the searhc equals a UUID test_uuid = None try: test_uuid = uuid.UUID(search) except: pass if test_uuid is not None: stocks_in_storage = stocks_in_storage.filter(Q(component__id = test_uuid) | Q(id= test_uuid)) else: stocks_in_storage = stocks_in_storage.filter(Q(component__name__icontains = search) | Q(component__package__name__icontains = search) | Q(component__manufacturer__name__icontains = search)) return stocks_in_storage def get_context_data(self, **kwargs): self.base_title = 'Stocks / ' + self.object.name context = super().get_context_data(**kwargs) context['breadcrumbs'] = self.get_breadcrumbs() storage_page = self.request.GET.get('storage_page', default=1) storage_paginator = Paginator(Storage.objects.filter(parent_storage=self.object), self.default_pagination_size) stock_search_input = self.request.GET.get('search') componente_stock_page = self.request.GET.get('stock_page', default=1) stock_paginator = Paginator(self.search_stock_queryset(stock_search_input), self.default_pagination_size) context['storages'] = storage_paginator.get_page(storage_page) stocks = stock_paginator.get_page(componente_stock_page) context['stocks'] = stocks context['stock_search'] = stock_search_input add_storage_form = AddSubStorageForm() add_storage_form.fields['responsible'].initial = self.request.user.username context['add_storage_form'] = add_storage_form context['delete_storage_error'] = None context['add_stock_form'] = AddStockForm() return context def handle_add_storage_post(self, request, **kwargs): f = AddSubStorageForm(data=request.POST) if f.is_valid(): sub_name = f.cleaned_data['storage_name'] try: user = User.objects.get(username=f.cleaned_data['responsible']) try: Storage.objects.create(name=sub_name, parent_storage=self.object, responsible=user) except ValidationError as v_err: f.add_error('storage_name', '. '.join(v_err.messages)) except: f.add_error('responsible', 'Invalid user') context = self.get_context_data(**kwargs) context['add_storage_form'] = f return self.render_to_response(context) def handle_del_storage_post(self, request, **kwargs): parent = self.object.parent_storage try: self.object.delete() except: context = self.get_context_data(**kwargs) context['delete_storage_errors'] = ['Error deleting Storage '+str(self.object)] return self.render_to_response(context) if parent is None: return redirect('parts-stocks') else: return redirect(reverse('parts-stocks-detail', kwargs={'uuid':parent.id})) def handle_del_stock_post(self, request, **kwargs): del_error = None if 'stock_uuid' in request.POST: f = DeleteStockForm(data=request.POST) if f.is_valid(): try: s = Stock.objects.get(id=f.cleaned_data['stock_uuid']) print(s.storage) print(self.object) if s.storage == self.object: s.delete() else: del_error = 'Cannot delete stock from another storage.' except: del_error = 'Could not find requested stock in this storage.' context = self.get_context_data(**kwargs) return self.render_to_response(context) def handle_update_watermark(self, request, **kwargs): edit_form = EditWatermarkForm(data=request.POST) update_watermark_error = None if edit_form.is_valid(): edit_form.save() else: pass # Todo: Handle error context = self.get_context_data(**kwargs) return self.render_to_response(context) def handle_amount_change_post(self, request, increase, **kwargs): edit_form = EditStockAmountForm(data=request.POST) if edit_form.is_valid(): edit_form.save(increase) context = self.get_context_data(**kwargs) return self.render_to_response(context) def handle_add_stock_post(self, request, **kwargs): f = AddStockForm(data=request.POST) error_occured = False if f.is_valid(): try: f.save(self.object) except Exception as ex: f.add_error('', str(ex)) error_occured = True else: error_occured = True context = self.get_context_data(**kwargs) if error_occured: context['add_stock_form'] = f return self.render_to_response(context) def post(self, request, *args, **kwargs): self.object = self.get_object() if 'submit-add-storage' in request.POST: return self.handle_add_storage_post(request, **kwargs) elif 'submit-delete-storage' in request.POST: return self.handle_del_storage_post(request, **kwargs) elif 'submit-delete-stock' in request.POST: return self.handle_del_stock_post(request, **kwargs) elif 'submit-edit-watermark' in request.POST: return self.handle_update_watermark(request, **kwargs) elif 'submit-amount-reduce' in request.POST: return self.handle_amount_change_post(request, False, **kwargs) elif 'submit-amount-increase' in request.POST: return self.handle_amount_change_post(request, True, **kwargs) elif 'submit-add-stock' in request.POST: return self.handle_add_stock_post(request, **kwargs) return super().post(request, *args, **kwargs) class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): template_name = 'parts/components-detail.html' model = Component pk_url_kwarg = 'uuid' base_title = '' navbar_selected = 'Components' def prepare_initial_param_formset_data(self): parameters = ComponentParameter.objects.filter(component=self.object) initdata = [] for param in parameters: param_type = param.parameter_type.parameter_type if param_type == 'F': value = param.text_value else: value = param.value initdata.append({'parameter_type': param.parameter_type, 'value': value}) return initdata def get_context_data(self, **kwargs): self.base_title = 'Component / '+self.object.name context = super().get_context_data(**kwargs) context['component'] = self.object context['stocks'] = Stock.objects.filter(component=self.object) context['comp_form'] = ComponentForm(instance=self.object) return context def handle_submit_edit_component_post(self, request, **kwargs): cform = ComponentForm(instance=self.object, data=request.POST, files=request.FILES) if cform.is_valid(): cform.save() context = self.get_context_data(**kwargs) if not cform.is_valid(): context['comp_form'] = cform return self.render_to_response(context) def handle_submit_delete_post(self, request, **kwargs): delete_error = None protected_stuff = None try: self.object.delete() except ProtectedError as pe: delete_error = 'Component is protected' protected_stuff = pe.protected_objects except: delete_error = 'Cannot delete component. Unknown error' if delete_error is None: return redirect('parts-components') else: context = self.get_context_data(**kwargs) context['delete_error'] = delete_error context['protected_stuff'] = protected_stuff return self.render_to_response(context) def post(self, request, *args, **kwargs): self.object = self.get_object() if 'submit-edit-component' in request.POST: return self.handle_submit_edit_component_post(request, **kwargs) elif 'submit-component-delete' in request.POST: return self.handle_submit_delete_post(request, **kwargs) else: return super().post(request, *args, **kwargs) class PackageDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): template_name = 'parts/packages-detail.html' model = Package pk_url_kwarg = 'uuid' base_title = '' navbar_selected = 'Packages' def get_context_data(self, **kwargs): self.base_title = 'Package / '+self.object.name context = super().get_context_data(**kwargs) context['package'] = self.object context['edit_form'] = PackageForm(instance=self.object) return context def handle_delete_package(self, request): delete_error = None protected_objects = None # Try to delete this instance try: self.object.delete() except ProtectedError as pe: delete_error = 'Cannot delete this package. It is referenced by a component.' protected_objects = pe.protected_objects except: delete_error = 'Cannot delete this package. Unknown error' if delete_error: context = self.get_context_data() context['delete_error'] = delete_error context['protected_components'] = protected_objects return self.render_to_response(context) else: return redirect('parts-packages') def edit_package(self, request): edit_form = PackageForm(data=request.POST, files=request.FILES, instance=self.object) if edit_form.is_valid(): edit_form.save() context = self.get_context_data() if not edit_form.is_valid(): context['edit_form'] = edit_form return self.render_to_response(context) def post(self, request, *args, **kwargs): self.object = self.get_object() if 'submit-pkg-delete' in request.POST: return self.handle_delete_package(request) elif 'submit-pkg-edit' in request.POST: return self.edit_package(request) return super().post(request, *args, **kwargs) class DistributorDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): template_name = 'parts/distributors-detail.html' model = Distributor pk_url_kwarg = 'uuid' base_title = '' navbar_selected = 'Distributors' def get_context_data(self, **kwargs): self.base_title = 'Distributor / '+self.object.name context = super().get_context_data(**kwargs) context['distributor'] = self.object context['edit_form'] = DistributorForm(instance=self.object) return context def handle_delete_distributor(self, request): delete_error = None protected_objects = None # Try to delete this instance try: self.object.delete() except ProtectedError as pe: delete_error = 'Cannot delete this distributor. It is referenced by a component.' protected_objects = pe.protected_objects except: delete_error = 'Cannot delete this distributor. Unknown error' if delete_error: context = self.get_context_data() context['delete_error'] = delete_error context['protected_components'] = protected_objects return self.render_to_response(context) else: return redirect('parts-distributors') def edit_distributor(self, request): edit_form = DistributorForm(data=request.POST, files=request.FILES, instance=self.object) if edit_form.is_valid(): edit_form.save() context = self.get_context_data() if not edit_form.is_valid(): context['edit_form'] = edit_form return self.render_to_response(context) def post(self, request, *args, **kwargs): self.object = self.get_object() if 'submit-distri-delete' in request.POST: return self.handle_delete_distributor(request) elif 'submit-distri-edit' in request.POST: return self.edit_distributor(request) return super().post(request, *args, **kwargs) class ManufacturersViewSet(LoginRequiredMixin, BaseTemplateMixin, TemplateView): template_name = 'parts/manufacturers.html' base_title = 'Manufacturers' navbar_selected = 'Manufacturers' default_page_size = 25 def search_manufacturers(self, search): qs = Manufacturer.objects.all() if not search: return qs search_fragements = search.strip().split() for search in search_fragements: qs = qs.filter(Q(name__icontains = search) | Q(website__icontains = search)) return qs def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) page_num = self.request.GET.get('page', default=1) search_string = self.request.GET.get('search', default=None) queryset = self.search_manufacturers(search_string) paginator = Paginator(queryset, self.default_page_size) context['search_string'] = search_string context['manufacturers'] = paginator.get_page(page_num) context['new_manufacturer_form'] = ManufacturerForm() return context def handle_add_new_manufacturer(self, request): form = ManufacturerForm(data=request.POST, files=request.FILES) if form.is_valid(): form.save() context = self.get_context_data() if not form.is_valid(): context['new_manufacturer_form'] = form return self.render_to_response(context) def post(self, request, *args, **kwargs): if 'submit-manufacturer-add-new' in request.POST: return self.handle_add_new_manufacturer(request) return super().post(request, *args, **kwargs) class ManufacturerDetailViewSet(LoginRequiredMixin, BaseTemplateMixin, DetailView): template_name = 'parts/manufacturers-detail.html' model = Manufacturer pk_url_kwarg = 'uuid' base_title = '' navbar_selected = 'Manufacturers' def get_context_data(self, **kwargs): self.base_title = 'Manufacturer / '+self.object.name context = super().get_context_data(**kwargs) context['manufacturer'] = self.object context['edit_form'] = ManufacturerForm(instance=self.object) return context def handle_delete_manufacturer(self, request): delete_error = None protected_objects = None # Try to delete this instance try: self.object.delete() except ProtectedError as pe: delete_error = 'Cannot delete this distributor. It is referenced by a component.' protected_objects = pe.protected_objects except: delete_error = 'Cannot delete this distributor. Unknown error' if delete_error: context = self.get_context_data() context['delete_error'] = delete_error context['protected_components'] = protected_objects return self.render_to_response(context) else: return redirect('parts-manufacturers') def edit_manufacturer(self, request): edit_form = ManufacturerForm(data=request.POST, files=request.FILES, instance=self.object) if edit_form.is_valid(): edit_form.save() context = self.get_context_data() if not edit_form.is_valid(): context['edit_form'] = edit_form return self.render_to_response(context) def post(self, request, *args, **kwargs): self.object = self.get_object() if 'submit-manufacturer-delete' in request.POST: return self.handle_delete_manufacturer(request) elif 'submit-manufacturer-edit' in request.POST: return self.edit_manufacturer(request) return super().post(request, *args, **kwargs)