sst/storage_templates #3

Merged
sst merged 5 commits from sst/storage_templates into develop 2024-11-15 22:58:18 +01:00
16 changed files with 215 additions and 134 deletions

View File

@ -1,11 +1,12 @@
from django.contrib.auth.models import User, Group from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from rest_framework import serializers from rest_framework import serializers
from parts import models as parts_models from parts import models as parts_models
class UserSerializer(serializers.HyperlinkedModelSerializer): class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = User model = get_user_model()
mhu marked this conversation as resolved
Review

Mh. According to https://stackoverflow.com/a/24630589/5761843
another way of retreiving the user model should be used.

get_user_model cannot guarantee that the User model is already loaded into the app cache. It might work in your specific setup, but it is a hit-and-miss scenario. If you change some settings (e.g. the order of INSTALLED_APPS) it might very well break the import and you will have to spend additional time debugging.

settings.AUTH_USER_MODEL will pass a string as the foreign key model, and if the retrieval of the model class fails at the time this foreign key is imported, the retrieval will be delayed until all model classes are loaded into the cache.

Mh. According to https://stackoverflow.com/a/24630589/5761843 another way of retreiving the user model should be used. > get_user_model cannot guarantee that the User model is already loaded into the app cache. It might work in your specific setup, but it is a hit-and-miss scenario. If you change some settings (e.g. the order of INSTALLED_APPS) it might very well break the import and you will have to spend additional time debugging. >settings.AUTH_USER_MODEL will pass a string as the foreign key model, and if the retrieval of the model class fails at the time this foreign key is imported, the retrieval will be delayed until all model classes are loaded into the cache.
Review

Ah. shit. Nevermind. Very old post. They changed that way back in django 1.xxx

Ah. shit. Nevermind. Very old post. They changed that way back in django 1.xxx
fields = ['username', 'email', 'first_name', 'last_name', 'groups'] fields = ['id', 'username', 'email', 'first_name', 'last_name', 'groups']
class GroupSerializer(serializers.HyperlinkedModelSerializer): class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:

View File

@ -7,6 +7,7 @@ router = routers.DefaultRouter()
router.register(r'users', UserViewSet) router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet) router.register(r'groups', GroupViewSet)
router.register(r'parts/storages', PartsStorageViewSet) router.register(r'parts/storages', PartsStorageViewSet)
router.register(r'parts/storage_templates', PartsStorageTemplatesViewSet, basename='storage-template')
router.register(r'parts/components', PartsComponentViewSet) router.register(r'parts/components', PartsComponentViewSet)
router.register(r'parts/stocks', PartsStockViewSet) router.register(r'parts/stocks', PartsStockViewSet)
router.register(r'parts/packages', PartsPackageViewSet) router.register(r'parts/packages', PartsPackageViewSet)

View File

@ -1,5 +1,6 @@
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.models import User, Group from django.contrib.auth.models import Group
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from rest_framework import viewsets, status from rest_framework import viewsets, status
from rest_framework import permissions from rest_framework import permissions
@ -25,7 +26,7 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet):
""" """
API endpoint that allows users to be viewed or edited. API endpoint that allows users to be viewed or edited.
""" """
queryset = User.objects.all() queryset = get_user_model().objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.SearchFilter] filter_backends = [filters.SearchFilter]
@ -44,8 +45,17 @@ class PartsStorageViewSet(viewsets.ModelViewSet):
serializer_class = StorageSerializer serializer_class = StorageSerializer
permission_classes = [permissions.DjangoModelPermissions] permission_classes = [permissions.DjangoModelPermissions]
filter_backends = [django_filters.rest_framework.DjangoFilterBackend] filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
search_fields = ['id', 'name', 'parent_storage']
filterset_fields = ['id', 'name', 'parent_storage'] filterset_fields = ['id', 'name', 'parent_storage']
class PartsStorageTemplatesViewSet(viewsets.ReadOnlyModelViewSet):
queryset = parts_models.Storage.objects.filter(is_template=True)
serializer_class = StorageSerializer
permission_classes = [permissions.IsAuthenticated]
filter_backends = [filters.SearchFilter]
search_fields = ['id', 'name']
filterset_fields = ['id', 'name']
class PartsComponentViewSet(viewsets.ModelViewSet): class PartsComponentViewSet(viewsets.ModelViewSet):
queryset = parts_models.Component.objects.all() queryset = parts_models.Component.objects.all()
serializer_class = ComponentSerializer serializer_class = ComponentSerializer

View File

@ -1,23 +1,26 @@
from django import forms from django import forms
from django.forms import widgets from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.forms import widgets
from parts import models as parts_models from parts import models as parts_models
from shimatta_modules.EngineeringNumberConverter import EngineeringNumberConverter from shimatta_modules.EngineeringNumberConverter import EngineeringNumberConverter
import uuid import uuid
from django.urls import reverse from django.urls import reverse
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, Row, Column from crispy_forms.layout import Layout, Row, Column
class AutoCompleteWidget(widgets.Input): class AutoCompleteWidget(widgets.Input):
template_name = 'widgets/autocomplete-foreign-key.html' template_name = 'widgets/autocomplete-foreign-key.html'
def __init__(self, api_search_url, image_field_name, foreign_model, name_field_name, *args, **kwargs): def __init__(self, api_search_url, image_field_name, foreign_model, name_field_name, prepend=None, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.image_field_name = image_field_name self.image_field_name = image_field_name
self.foreign_model = foreign_model self.foreign_model = foreign_model
self.api_search_url = api_search_url self.api_search_url = api_search_url
self.name_field_name = name_field_name self.name_field_name = name_field_name
self.prepend = prepend
def get_context(self, name, value, attrs): def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs) context = super().get_context(name, value, attrs)
@ -47,14 +50,25 @@ class AutoCompleteWidget(widgets.Input):
'current_instance': instance, 'current_instance': instance,
'image': image, 'image': image,
'name_field_name': self.name_field_name, 'name_field_name': self.name_field_name,
'prepend': self.prepend,
'name': display_name, 'name': display_name,
} }
return context return context
class AutocompleteForeingKeyField(forms.UUIDField): class AutocompleteForeingKeyField(forms.UUIDField):
def __init__(self, foreign_model=None, api_search_url=None, image_field_name='image', name_field_name='name', **kwargs): def __init__(self,
foreign_model=None,
api_search_url=None,
image_field_name='image',
name_field_name='name',
prepend=None,
**kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.widget = AutoCompleteWidget(api_search_url, image_field_name, foreign_model, name_field_name) self.widget = AutoCompleteWidget(api_search_url,
image_field_name,
foreign_model,
name_field_name,
prepend)
self.foreign_model = foreign_model self.foreign_model = foreign_model
@ -74,13 +88,24 @@ class AutocompleteForeingKeyField(forms.UUIDField):
raise ValidationError('Given element does not exist') raise ValidationError('Given element does not exist')
return obj return obj
class MyTestForm(forms.Form): class MyTestForm(forms.Form):
pass pass
class AddSubStorageForm(forms.Form): class ChangeStorageForm(forms.Form):
storage_name = forms.CharField(label="storage_name", initial='') storage_name = forms.CharField(label="storage_name", initial='')
responsible = forms.CharField(label='responsible_user') responsible = AutocompleteForeingKeyField(api_search_url='user-list',
image_field_name=None,
name_field_name='username',
foreign_model=get_user_model(),
prepend='@')
is_template = forms.BooleanField(label='is_template', required=False)
class AddSubStorageForm(ChangeStorageForm):
template = AutocompleteForeingKeyField(api_search_url='storage-template-list',
image_field_name=None,
foreign_model=parts_models.Storage,
required=False)
class DeleteStockForm(forms.Form): class DeleteStockForm(forms.Form):
stock_uuid = forms.UUIDField() stock_uuid = forms.UUIDField()

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.5 on 2024-11-10 12:42
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('parts', '0010_auto_20220103_1606'),
]
operations = [
migrations.AddField(
model_name='storage',
name='is_template',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='storage',
name='template',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='template_of', to='parts.storage'),
),
]

View File

@ -1,7 +1,7 @@
from django.db import models from django.db import models
from shimatta_modules import RandomFileName from shimatta_modules import RandomFileName
from django.db.models import F, Sum from django.db.models import F, Sum
from django.contrib.auth.models import User as AuthUser from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.dispatch import receiver from django.dispatch import receiver
@ -63,7 +63,15 @@ class Storage(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
name = models.CharField(max_length=100, validators=[storage_name_validator]) name = models.CharField(max_length=100, validators=[storage_name_validator])
parent_storage = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True) parent_storage = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True)
responsible = models.ForeignKey(AuthUser, on_delete=models.SET_NULL, blank=True, null=True) responsible = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, blank=True, null=True)
# allow storages to be templates which can be selected when adding new storages
is_template = models.BooleanField(default=False)
template = models.ForeignKey('self',
on_delete=models.SET_NULL,
blank=True,
null=True,
related_name='template_of')
def get_path_components(self): def get_path_components(self):
chain = [] chain = []
@ -288,7 +296,7 @@ class DistributorNum(models.Model):
class QrPrintJob(models.Model): class QrPrintJob(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
user = models.ForeignKey(AuthUser, on_delete=models.CASCADE, null=False, blank=False) user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE, null=False, blank=False)
qrdata = models.CharField(max_length=256, blank=True, null=False) qrdata = models.CharField(max_length=256, blank=True, null=False)
text = models.TextField(max_length=512, blank=True, null=False) text = models.TextField(max_length=512, blank=True, null=False)
print_count = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0), MaxValueValidator(32)]) print_count = models.PositiveIntegerField(default=1, validators=[MinValueValidator(0), MaxValueValidator(32)])
@ -336,3 +344,19 @@ def auto_delete_file_on_change(sender, instance, **kwargs):
return True return True
if os.path.isfile(old_file.path): if os.path.isfile(old_file.path):
os.remove(old_file.path) os.remove(old_file.path)
@receiver(models.signals.post_save, sender=Storage)
def auto_apply_template_structure(sender, instance, created, **kwargs):
"""
Add the sub-storages from the template.
If there are nested sub-storages these will be added when the sub-storages
are created automatically.
"""
if created:
if instance.template:
for sub_storage in instance.template.storage_set.all():
Storage.objects.create(name=sub_storage.name,
parent_storage=instance,
responsible=instance.responsible,
is_template=False,
template=sub_storage)

View File

@ -1,13 +1,10 @@
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.urls import resolve, reverse from django.urls import reverse
from django.contrib.auth import logout, login from django.contrib.auth import logout, login
from django.contrib.auth.models import User
from django.http import HttpResponse
from .navbar import NavBar from .navbar import NavBar
from django.contrib.auth.forms import AuthenticationForm as AuthForm from django.contrib.auth.forms import AuthenticationForm as AuthForm
from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash from django.contrib.auth import update_session_auth_hash
from django.views import View
import django.forms as forms import django.forms as forms
from django.views.generic import TemplateView, DetailView from django.views.generic import TemplateView, DetailView
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
@ -400,36 +397,23 @@ class StockView(LoginRequiredMixin, BaseTemplateMixin, TemplateView):
context['low_stocks'] = low_stock_paginator.get_page(low_stock_page) context['low_stocks'] = low_stock_paginator.get_page(low_stock_page)
context['storages'] = storage_paginator.get_page(storage_page) context['storages'] = storage_paginator.get_page(storage_page)
add_stor_form = AddSubStorageForm() add_stor_form = AddSubStorageForm()
add_stor_form.fields['responsible'].initial = self.request.user.username add_stor_form.fields['responsible'].initial = self.request.user.id
context['add_storage_form'] = add_stor_form context['add_storage_form'] = add_stor_form
return context return context
def handle_add_storage(self, request, **kwargs): def handle_add_storage(self, request, **kwargs):
return_invalid_form = False
f = AddSubStorageForm(data=request.POST) f = AddSubStorageForm(data=request.POST)
if f.is_valid(): if f.is_valid():
new_storage_name = f.cleaned_data['storage_name'] sub_name = f.cleaned_data['storage_name']
try: try:
resp_user = User.objects.get(username=f.cleaned_data['responsible']) Storage.objects.create(name=sub_name,
except Exception as _: responsible=f.cleaned_data['responsible'],
resp_user = None is_template=f.cleaned_data['is_template'],
f.add_error('responsible', 'Invalid Responsible User') template=f.cleaned_data.get('template'))
return_invalid_form = True except ValidationError as v_err:
f.add_error('storage_name', '. '.join(v_err.messages))
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) context = self.get_context_data(**kwargs)
if return_invalid_form:
context['add_storage_form'] = f context['add_storage_form'] = f
return self.render_to_response(context) return self.render_to_response(context)
def post(self, request, **kwargs): def post(self, request, **kwargs):
@ -496,8 +480,13 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
context['stocks'] = stocks context['stocks'] = stocks
context['stock_search'] = stock_search_input context['stock_search'] = stock_search_input
add_storage_form = AddSubStorageForm() add_storage_form = AddSubStorageForm()
add_storage_form.fields['responsible'].initial = self.request.user.username add_storage_form.fields['responsible'].initial = self.request.user.id
context['add_storage_form'] = add_storage_form context['add_storage_form'] = add_storage_form
change_storage_form = ChangeStorageForm()
change_storage_form.fields['storage_name'].initial = self.object.name
change_storage_form.fields['responsible'].initial = self.object.responsible.id
change_storage_form.fields['is_template'].initial = self.object.is_template
context['change_storage_form'] = change_storage_form
context['delete_storage_error'] = None context['delete_storage_error'] = None
context['add_stock_form'] = AddStockForm() context['add_stock_form'] = AddStockForm()
@ -508,17 +497,32 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
if f.is_valid(): if f.is_valid():
sub_name = f.cleaned_data['storage_name'] sub_name = f.cleaned_data['storage_name']
try: try:
user = User.objects.get(username=f.cleaned_data['responsible']) Storage.objects.create(name=sub_name,
try: parent_storage=self.object,
Storage.objects.create(name=sub_name, parent_storage=self.object, responsible=user) responsible=f.cleaned_data['responsible'],
is_template=f.cleaned_data['is_template'],
template=f.cleaned_data.get('template'))
except ValidationError as v_err: except ValidationError as v_err:
f.add_error('storage_name', '. '.join(v_err.messages)) f.add_error('storage_name', '. '.join(v_err.messages))
except:
f.add_error('responsible', 'Invalid user')
context = self.get_context_data(**kwargs) context = self.get_context_data(**kwargs)
context['add_storage_form'] = f context['add_storage_form'] = f
return self.render_to_response(context) return self.render_to_response(context)
def handle_change_storage_post(self, request, **kwargs):
f = ChangeStorageForm(data=request.POST)
if f.is_valid():
sub_name = f.cleaned_data['storage_name']
try:
self.object.name = f.cleaned_data['storage_name']
self.object.responsible = f.cleaned_data['responsible']
self.object.is_template = f.cleaned_data['is_template']
self.object.save()
except ValidationError as v_err:
f.add_error('storage_name', '. '.join(v_err.messages))
context = self.get_context_data(**kwargs)
context['change_storage_form'] = f
return self.render_to_response(context)
def handle_del_storage_post(self, request, **kwargs): def handle_del_storage_post(self, request, **kwargs):
parent = self.object.parent_storage parent = self.object.parent_storage
try: try:
@ -532,6 +536,7 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
return redirect('parts-stocks') return redirect('parts-stocks')
else: else:
return redirect(reverse('parts-stocks-detail', kwargs={'uuid':parent.id})) return redirect(reverse('parts-stocks-detail', kwargs={'uuid':parent.id}))
def handle_del_stock_post(self, request, **kwargs): def handle_del_stock_post(self, request, **kwargs):
del_error = None del_error = None
if 'stock_uuid' in request.POST: if 'stock_uuid' in request.POST:
@ -593,6 +598,8 @@ class StockViewDetail(LoginRequiredMixin, BaseTemplateMixin, DetailView):
if 'submit-add-storage' in request.POST: if 'submit-add-storage' in request.POST:
return self.handle_add_storage_post(request, **kwargs) return self.handle_add_storage_post(request, **kwargs)
elif 'submit-change-storage' in request.POST:
return self.handle_change_storage_post(request, **kwargs)
elif 'submit-delete-storage' in request.POST: elif 'submit-delete-storage' in request.POST:
return self.handle_del_storage_post(request, **kwargs) return self.handle_del_storage_post(request, **kwargs)
elif 'submit-delete-stock' in request.POST: elif 'submit-delete-stock' in request.POST:

View File

@ -12,9 +12,9 @@ function initialize_autocompletion_foreign_key_field(search_element) {
var name_field_name = search_element.getAttribute('data-ac-name-field'); var name_field_name = search_element.getAttribute('data-ac-name-field');
var search_url = search_element.getAttribute('data-ac-url'); var search_url = search_element.getAttribute('data-ac-url');
var base_id = search_element.getAttribute('id'); var base_id = search_element.getAttribute('id');
var uuid_field = search_element.parentElement.querySelector('#'+base_id+'-uuid-field'); var uuid_field = search_element.parentElement.parentElement.querySelector('#'+base_id+'-uuid-field');
var dflex_container = search_element.parentElement.querySelector('#'+base_id+'-dflex-container'); var dflex_container = search_element.parentElement.parentElement.querySelector('#'+base_id+'-dflex-container');
var initial_delete_button = search_element.parentElement.querySelector('[data-ac-delete]'); var initial_delete_button = search_element.parentElement.parentElement.querySelector('[data-ac-delete]');
console.log(initial_delete_button); console.log(initial_delete_button);
console.log(image_field_name); console.log(image_field_name);

View File

@ -29,6 +29,10 @@ function api_search_user(search, onSuccess, onFail) {
return api_ajax_request_without_send('GET', api_urls_v1['user-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail); return api_ajax_request_without_send('GET', api_urls_v1['user-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail);
} }
function api_search_storage_template(search, onSuccess, onFail) {
return api_ajax_request_without_send('GET', api_urls_v1['storage-template-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail);
}
function api_search_component(search, onSuccess, onFail) { function api_search_component(search, onSuccess, onFail) {
return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail); return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail);
} }

View File

@ -65,6 +65,7 @@
'user-list': '{% url 'user-list' %}', 'user-list': '{% url 'user-list' %}',
'groups-list': '{% url 'user-list' %}', 'groups-list': '{% url 'user-list' %}',
'storage-list': '{% url 'storage-list' %}', 'storage-list': '{% url 'storage-list' %}',
'storage-template-list': '{% url 'storage-template-list' %}',
'component-list': '{% url 'component-list' %}', 'component-list': '{% url 'component-list' %}',
'package-list': '{% url 'package-list' %}', 'package-list': '{% url 'package-list' %}',
'stock-list': '{% url 'stock-list' %}', 'stock-list': '{% url 'stock-list' %}',

View File

@ -0,0 +1,22 @@
{% load static %}
{% load crispy_forms_tags %}
<div class="modal fade" id="add-sub-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Storage</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
{{form|crispy}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<input type="submit" class="btn btn-primary" value="Add Storage" name="submit-add-storage">
</div>
</form>
</div>
</div>
</div>

View File

@ -0,0 +1,22 @@
{% load static %}
{% load crispy_forms_tags %}
<div class="modal fade" id="change-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Change Storage</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
{{form|crispy}}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<input type="submit" class="btn btn-primary" value="Change Storage" name="submit-change-storage">
</div>
</form>
</div>
</div>
</div>

View File

@ -1,46 +0,0 @@
{% load static %}
<div class="modal fade" id="add-sub-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Add Storage</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form action="" method="post">
{% csrf_token %}
<div class="modal-body">
<label for="add-storage-name" class="form-label">Storage Name</label>
<div class="input-group has-validation">
<input value="{{form.storage_name.value}}" class="form-control{% if form.storage_name.errors or form.non_field_errors %} is-invalid{% endif %}" id="add-storage-name" name="{{form.storage_name.name}}" type="text" aria-describedby="validationStorageName" required>
<div id="validationStorageName" class="invalid-feedback">
{% for msg in form.storage_name.errors %}
{{msg}}
{% endfor %}
</div>
</div>
<label for="{{form.responsible.id_for_label}}">Responsible</label>
<div class="input-group has-validation dropdown">
<span class="input-group-text" id="add_storage_username_prepend">@</span><input autocomplete="off" data-bs-toggle="dropdown" type="text" value="{{form.responsible.value}}" class="form-control{% if form.responsible.errors or form.non_field_errors %} is-invalid{% endif %}" id="{{form.responsible.id_for_label}}" name="{{form.responsible.name}}" aria-describedby="add_storage_username_prepend validationServerUsernameFeedback" required>
<ul class="dropdown-menu" aria-labelledby="{{form.responsible.id_for_label}}" id="{{form.responsible.id_for_label}}-ac-dropdown">
</ul>
<div id="validationServerUsernameFeedback" class="invalid-feedback">
{% for msg in form.responsible.errors %}
{{msg}}
{% endfor %}
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<input type="submit" class="btn btn-primary" value="Add Storage" name="submit-add-storage">
{% if form.non_field_errors %}
{% for error in form.non_field_errors %}
<p class="text-danger text-center">{{ error }}</p>
{% endfor %}
{% endif %}
</div>
</form>
</div>
</div>
</div>

View File

@ -27,6 +27,7 @@
{% endif %} {% endif %}
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#delete-storage-modal">Delete</button> <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#delete-storage-modal">Delete</button>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-sub-modal"><i class="bi bi-plus-circle"></i></button> <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#add-sub-modal"><i class="bi bi-plus-circle"></i></button>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#change-modal"><i class="bi bi-pen-fill"></i></button>
</h1> </h1>
<div class="list-group"> <div class="list-group">
{% for storage in storages %} {% for storage in storages %}
@ -109,7 +110,11 @@
{% endfor %} {% endfor %}
<!-- Modal for adding a substorage--> <!-- Modal for adding a substorage-->
{% with add_storage_form as form %} {% with add_storage_form as form %}
{% include 'parts/modals/new-substorage-modal.html' %} {% include 'parts/modals/add-substorage-modal.html' %}
{% endwith %}
<!-- Modal to change current storag-->
{% with change_storage_form as form %}
{% include 'parts/modals/change-storage-modal.html' %}
{% endwith %} {% endwith %}
<!-- Modal for deleting this storage --> <!-- Modal for deleting this storage -->
{% with delete_storage_errors as err_msgs %} {% with delete_storage_errors as err_msgs %}
@ -158,18 +163,6 @@ api_get_component_from_id(uuid, function(component){
} }
{% endif %} {% endif %}
new AutocompleteText('{{add_storage_form.responsible.id_for_label}}', '{{add_storage_form.responsible.id_for_label}}-ac-dropdown',
function(search, autocomplete_obj) {
api_search_user(search, function(results) {
var usernames = new Array();
console.log(results);
for (var i = 0; i < results.results.length; i++) {
usernames.push(results.results[i].username);
}
console.log(usernames);
autocomplete_obj.show_results(usernames);
}, function(){});
});
</script> </script>
{% endblock custom_scripts %} {% endblock custom_scripts %}

View File

@ -54,7 +54,7 @@
<!-- Add storage modal form --> <!-- Add storage modal form -->
{% with add_storage_form as form %} {% with add_storage_form as form %}
{% include 'parts/modals/new-substorage-modal.html' %} {% include 'parts/modals/add-substorage-modal.html' %}
{% endwith %} {% endwith %}
</div> </div>
@ -67,18 +67,6 @@
var modal = bootstrap.Modal.getOrCreateInstance(addSubStorageModal); var modal = bootstrap.Modal.getOrCreateInstance(addSubStorageModal);
modal.show(); modal.show();
{% endif %} {% endif %}
new AutocompleteText('{{add_storage_form.responsible.id_for_label}}', '{{add_storage_form.responsible.id_for_label}}-ac-dropdown',
function(search, autocomplete_obj) {
api_search_user(search, function(results) {
var usernames = new Array();
console.log(results);
for (var i = 0; i < results.results.length; i++) {
usernames.push(results.results[i].username);
}
console.log(usernames);
autocomplete_obj.show_results(usernames);
}, function(){});
});
</script> </script>
{% endblock custom_scripts %} {% endblock custom_scripts %}

View File

@ -1,7 +1,12 @@
<div class="dropdown"> <div class="dropdown">
<div class="input-group">
{% if custom.prepend %}
<span class="input-group-text" id="{{widget.attrs.id}}-prepend">{{custom.prepend}}</span>
{% endif %}
<input autocomplete="off" id="{{widget.attrs.id}}" data-ac-url="{{custom.search_url}}" data-ac-name-field="{{custom.name_field_name}}" {% if custom.image_field_name %}data-ac-image-field="{{custom.image_field_name}}"{% endif %} data-bs-toggle="dropdown" type="text" placeholder="Search..." class="{{widget.attrs.class}}"> <input autocomplete="off" id="{{widget.attrs.id}}" data-ac-url="{{custom.search_url}}" data-ac-name-field="{{custom.name_field_name}}" {% if custom.image_field_name %}data-ac-image-field="{{custom.image_field_name}}"{% endif %} data-bs-toggle="dropdown" type="text" placeholder="Search..." class="{{widget.attrs.class}}">
<ul id="{{widget.attrs.id}}-ac-ul" class="dropdown-menu"> <ul id="{{widget.attrs.id}}-ac-ul" class="dropdown-menu">
</ul> </ul>
</div>
<div class="d-flex align-items-center mt-3 mb-3" id="{{widget.attrs.id}}-dflex-container"> <div class="d-flex align-items-center mt-3 mb-3" id="{{widget.attrs.id}}-dflex-container">
{% if custom.current_instance %} {% if custom.current_instance %}
{% if custom.image_field_name %} {% if custom.image_field_name %}