added templating mechanism for storages

This commit is contained in:
Stefan Strobel 2024-11-10 20:46:45 +01:00
parent 6dd781021c
commit 0b27e9f064
15 changed files with 180 additions and 121 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()
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,22 @@ 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 AddSubStorageForm(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)
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,7 +480,7 @@ 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
context['delete_storage_error'] = None context['delete_storage_error'] = None
context['add_stock_form'] = AddStockForm() context['add_stock_form'] = AddStockForm()
@ -508,13 +492,13 @@ 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'],
except ValidationError as v_err: is_template=f.cleaned_data['is_template'],
f.add_error('storage_name', '. '.join(v_err.messages)) template=f.cleaned_data.get('template'))
except: except ValidationError as v_err:
f.add_error('responsible', 'Invalid user') f.add_error('storage_name', '. '.join(v_err.messages))
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)
@ -532,6 +516,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:

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

@ -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

@ -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

@ -109,7 +109,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/substorage-modal.html' %}
{% endwith %}
<!-- Modal to change current storag-->
{% with change_storage_form as form %}
{% include 'parts/modals/substorage-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 +162,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/substorage-modal.html' %}
{% endwith %} {% endwith %}
</div> </div>
@ -80,6 +80,19 @@
}, function(){}); }, function(){});
}); });
new AutocompleteText('{{add_storage_form.template.id_for_label}}', '{{add_storage_form.template.id_for_label}}-ac-dropdown',
function(search, autocomplete_obj) {
api_search_storage_template(search, function(results) {
var templates = new Array();
console.log(results);
for (var i = 0; i < results.results.length; i++) {
templates.push(results.results[i].id);
}
console.log(templates);
autocomplete_obj.show_results(templates);
}, function(){});
});
</script> </script>
{% endblock custom_scripts %} {% endblock custom_scripts %}

View File

@ -1,7 +1,12 @@
<div class="dropdown"> <div class="dropdown">
<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}}"> <div class="input-group">
<ul id="{{widget.attrs.id}}-ac-ul" class="dropdown-menu"> {% if custom.prepend %}
</ul> <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}}">
<ul id="{{widget.attrs.id}}-ac-ul" class="dropdown-menu">
</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 %}