diff --git a/shimatta_kenkyusho/api/serializers.py b/shimatta_kenkyusho/api/serializers.py index ba8cc63..6ba6aaf 100644 --- a/shimatta_kenkyusho/api/serializers.py +++ b/shimatta_kenkyusho/api/serializers.py @@ -60,4 +60,14 @@ class StockIncrementDecrementSerializer(serializers.Serializer): class ManufacturerSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = parts_models.Manufacturer + fields = '__all__' + +class ComponentTypeSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = parts_models.ComponentType + fields = '__all__' + +class ComponentParameterTypeSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = parts_models.ComponentParameterType fields = '__all__' \ No newline at end of file diff --git a/shimatta_kenkyusho/api/urls.py b/shimatta_kenkyusho/api/urls.py index cfa57da..18b7644 100644 --- a/shimatta_kenkyusho/api/urls.py +++ b/shimatta_kenkyusho/api/urls.py @@ -12,6 +12,8 @@ router.register(r'parts/stocks', PartsStockViewSet) router.register(r'parts/packages', PartsPackageViewSet) router.register(r'parts/distributors', PartsDistributorviewSet) router.register(r'parts/manufacturers', PartsManufacturerViewSet) +router.register(r'parts/component-types', PartsComponentTypeViewSet) +router.register(r'parts/component-param-types', PartsComponentParameterTypeViewSet) urlpatterns = [ path('', include(router.urls)), diff --git a/shimatta_kenkyusho/api/views.py b/shimatta_kenkyusho/api/views.py index d2519ab..d98d54a 100644 --- a/shimatta_kenkyusho/api/views.py +++ b/shimatta_kenkyusho/api/views.py @@ -49,6 +49,20 @@ class PartsComponentViewSet(viewsets.ModelViewSet): filter_backends = [filters.SearchFilter] search_fields = ['id', 'name', 'package__name', 'manufacturer__name'] +class PartsComponentTypeViewSet(viewsets.ModelViewSet): + queryset = parts_models.ComponentType.objects.all() + serializer_class = ComponentTypeSerializer + permission_classes = [permissions.DjangoModelPermissions] + filter_backends = [filters.SearchFilter] + search_fields = ['class_name'] + +class PartsComponentParameterTypeViewSet(viewsets.ModelViewSet): + queryset = parts_models.ComponentParameterType.objects.all() + serializer_class = ComponentParameterTypeSerializer + permission_classes = [permissions.DjangoModelPermissions] + filter_backends = [filters.SearchFilter] + search_fields = ['name'] + class PartsManufacturerViewSet(viewsets.ModelViewSet): queryset = parts_models.Manufacturer.objects.all() serializer_class = ManufacturerSerializer @@ -77,12 +91,16 @@ class PartsStockViewSet(viewsets.ModelViewSet): class PartsPackageViewSet(viewsets.ModelViewSet): queryset = parts_models.Package.objects.all() serializer_class = PackageSerializer - permission_classes = [permissions.DjangoModelPermissions] + permission_classes = [permissions.DjangoModelPermissions] + filter_backends = [filters.SearchFilter] + search_fields = ['name'] class PartsDistributorviewSet(viewsets.ModelViewSet): queryset = parts_models.Distributor.objects.all() serializer_class = DistributorSerializer permission_classes = [permissions.DjangoModelPermissions] + filter_backends = [filters.SearchFilter] + search_fields = ['name'] ## Token Authentication views diff --git a/shimatta_kenkyusho/parts/forms.py b/shimatta_kenkyusho/parts/forms.py index 0e726ce..c66d67d 100644 --- a/shimatta_kenkyusho/parts/forms.py +++ b/shimatta_kenkyusho/parts/forms.py @@ -111,10 +111,10 @@ class EditComponentForm(forms.Form): description = forms.CharField(required=False, widget=forms.Textarea) # Look up these fields later. Will be autocompleted in UI - manufacturer = forms.CharField(required=False) - component_type = forms.CharField(required=False, label='Component Type') - pref_distri = forms.CharField(required=False, label='Preferred Distributor') - package = forms.CharField(required=False) + manufacturer = forms.CharField(required=False, initial='') + component_type = forms.CharField(required=False, label='Component Type', initial='') + pref_distri = forms.CharField(required=False, label='Preferred Distributor', initial='') + package = forms.CharField(required=False, initial='') image = forms.ImageField(required=False) diff --git a/shimatta_kenkyusho/parts/migrations/0002_alter_componenttype_possible_parameter.py b/shimatta_kenkyusho/parts/migrations/0002_alter_componenttype_possible_parameter.py new file mode 100644 index 0000000..9d6330b --- /dev/null +++ b/shimatta_kenkyusho/parts/migrations/0002_alter_componenttype_possible_parameter.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2021-11-11 19:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('parts', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='componenttype', + name='possible_parameter', + field=models.ManyToManyField(blank=True, to='parts.ComponentParameterType'), + ), + ] diff --git a/shimatta_kenkyusho/parts/models.py b/shimatta_kenkyusho/parts/models.py index ea43b3e..19d1f70 100644 --- a/shimatta_kenkyusho/parts/models.py +++ b/shimatta_kenkyusho/parts/models.py @@ -32,7 +32,7 @@ class ComponentType(models.Model): ordering = ['class_name'] class_name = models.CharField(max_length=50, unique=True) passive = models.BooleanField() - possible_parameter = models.ManyToManyField(ComponentParameterType) + possible_parameter = models.ManyToManyField(ComponentParameterType, blank=True) def __str__(self): return '[' + self.class_name + ']' diff --git a/shimatta_kenkyusho/parts/views.py b/shimatta_kenkyusho/parts/views.py index 9ad92dd..c0b14ed 100644 --- a/shimatta_kenkyusho/parts/views.py +++ b/shimatta_kenkyusho/parts/views.py @@ -15,6 +15,7 @@ from .models import Storage, Stock, Component, Distributor, Manufacturer, Packag from .qr_parser import QrCodeValidator from django.core.paginator import Paginator from django.core.exceptions import ValidationError +from django.db import IntegrityError from .forms import MyTestForm, AddSubStorageForm, DeleteStockForm, EditWatermarkForm, EditStockAmountForm, AddStockForm, EditComponentForm from django.db.models import Q from django.db.models.functions import Lower @@ -403,9 +404,15 @@ class ComponentDetailView(LoginRequiredMixin, BaseTemplateMixin, DetailView): form = EditComponentForm(instance=self.object, data=request.POST, files=request.FILES) if form.is_valid(): - form.save() + try: + form.save() + except IntegrityError as ie: + form.add_error('name', 'Component name, package, and manufacturer are not unique!') + form.add_error('package', 'Component name, package, and manufacturer are not unique!') + form.add_error('manufacturer', 'Component name, package, and manufacturer are not unique!') + form_error = True + self.object = self.get_object() else: - print("Error") form_error = True context = self.get_context_data(**kwargs) diff --git a/shimatta_kenkyusho/static/js/add-stock-modal.js b/shimatta_kenkyusho/static/js/add-stock-modal.js index 87ec682..a3fa868 100644 --- a/shimatta_kenkyusho/static/js/add-stock-modal.js +++ b/shimatta_kenkyusho/static/js/add-stock-modal.js @@ -36,23 +36,8 @@ function(search, autocomplete_obj) { var test = []; for (var i = 0; i < components.length; i++) { var c = components[i]; - var node = document.createElement('div'); - node.setAttribute('class', 'd-flex align-items-center'); - var img_container = document.createElement('div'); - img_container.setAttribute('class', 'flex-shrink-0'); + var text_container = document.createElement('div'); - text_container.setAttribute('class', 'flex-grow-1 ms-1'); - var img = document.createElement('img'); - var img_path = fallback_img_path; - var style = "width:64px;max-height:64px;"; - if (c.ro_image != null) { - img_path = c.ro_image; - style = "max-width:64px;max-height:64px;"; - } - img.setAttribute('src', img_path); - img.setAttribute('style', style) - img_container.appendChild(img); - var name_text = document.createTextNode(c.name); var heading = document.createElement('h6'); heading.appendChild(name_text); @@ -64,8 +49,7 @@ function(search, autocomplete_obj) { text_container.appendChild(document.createTextNode(' by '+c.ro_manufacturer_name)); } - node.appendChild(img_container); - node.appendChild(text_container); + node = AutocompleteCustomUi.create_media_div(c.ro_image, [text_container]) test.push({'ui': node, 'data': c}) } diff --git a/shimatta_kenkyusho/static/js/autocomplete.js b/shimatta_kenkyusho/static/js/autocomplete.js index dbe90d9..07cd4ba 100644 --- a/shimatta_kenkyusho/static/js/autocomplete.js +++ b/shimatta_kenkyusho/static/js/autocomplete.js @@ -1,6 +1,36 @@ const autocomplete_query_delay_ms = 60; class AutocompleteCustomUi { + + static create_media_div(img_src, text_nodes) { + var ui = document.createElement('div'); + ui.setAttribute('class', 'd-flex align-items-center'); + var img_div = document.createElement('div'); + img_div.setAttribute('class', 'flex-shrink-0'); + var img = document.createElement('img'); + if (img_src != null && img_src != undefined && img_src != '') { + img.setAttribute('src', img_src); + img.setAttribute('class', 'component-img-small'); + } else { + img.setAttribute('src', fallback_img_path); + img.setAttribute('class', 'component-img-def-small'); + } + + img_div.appendChild(img); + ui.appendChild(img_div); + var text_div = document.createElement('div'); + text_div.setAttribute('class', 'flex-grow-1 ms-3'); + + if (text_nodes != undefined) { + for (var i = 0; i < text_nodes.length; i++) { + text_div.appendChild(text_nodes[i]); + } + } + ui.appendChild(text_div) + + return ui + } + constructor(text_id, dropdown_id, query_function) { this.text_id = text_id; @@ -17,7 +47,7 @@ class AutocompleteCustomUi { /** * - * @param {*} nodes_to_add A liust of dictionaries containing the shown objects and the data called when clicked. + * @param {*} nodes_to_add A list of dictionaries containing the shown objects and the data called when clicked. * * The list: {{'ui': , 'data': 'my_data'}, {}, ...} * diff --git a/shimatta_kenkyusho/static/js/edit-component-modal.js b/shimatta_kenkyusho/static/js/edit-component-modal.js new file mode 100644 index 0000000..4b286f1 --- /dev/null +++ b/shimatta_kenkyusho/static/js/edit-component-modal.js @@ -0,0 +1,81 @@ +document.addEventListener("DOMContentLoaded", function(){ + // Create the autocompletion stuff + + new AutocompleteText(edit_comp_modal_ids['component_type'], edit_comp_modal_ids['component_type']+'-ac-ul', + function(search, autocomplete) { + api_search_component_types(search, function(result) { + type_names = []; + for(var i = 0; i < result.results.length; i++) { + var r = result.results[i]; + type_names.push(r.class_name); + } + autocomplete.show_results(type_names) + }, function() { + // Nothing to do--- + }) + }); + + new AutocompleteCustomUi(edit_comp_modal_ids['manufacturer'], edit_comp_modal_ids['manufacturer']+'-ac-ul', + function(search, autocomplete) { + api_search_manufacturer(search, function(result) { + nodes = []; + for (var i = 0; i < result.results.length; i++) { + var manu = result.results[i]; + + // Construct the ui: + ui = AutocompleteCustomUi.create_media_div(manu.image, [document.createTextNode(manu.name)]) + + nodes.push({'ui': ui, 'data': manu.name}); + } + + autocomplete.show_results(nodes, function(data) { + document.getElementById(edit_comp_modal_ids['manufacturer']).value = data; + }) + }, function() { + // Nothing to do--- + }) + }); + + new AutocompleteCustomUi(edit_comp_modal_ids['pref_distri'], edit_comp_modal_ids['pref_distri']+'-ac-ul', + function(search, autocomplete) { + api_search_distributor(search, function(result) { + nodes = []; + for (var i = 0; i < result.results.length; i++) { + var distri = result.results[i]; + + // Construct the ui: + ui = AutocompleteCustomUi.create_media_div(distri.image, [document.createTextNode(distri.name)]) + + nodes.push({'ui': ui, 'data': distri.name}); + } + + autocomplete.show_results(nodes, function(data) { + document.getElementById(edit_comp_modal_ids['pref_distri']).value = data; + }) + }, function() { + // Nothing to do--- + }) + }); + + new AutocompleteCustomUi(edit_comp_modal_ids['package'], edit_comp_modal_ids['package']+'-ac-ul', + function(search, autocomplete) { + api_search_package(search, function(result) { + nodes = []; + for (var i = 0; i < result.results.length; i++) { + var pkg = result.results[i]; + + // Construct the ui: + ui = AutocompleteCustomUi.create_media_div(pkg.image, [document.createTextNode(pkg.name)]) + + nodes.push({'ui': ui, 'data': pkg.name}); + } + + autocomplete.show_results(nodes, function(data) { + document.getElementById(edit_comp_modal_ids['package']).value = data; + }) + }, function() { + // Nothing to do--- + }) + }); + +}); \ No newline at end of file diff --git a/shimatta_kenkyusho/static/js/kenyusho-api-v1.js b/shimatta_kenkyusho/static/js/kenyusho-api-v1.js index 951efc0..d801c74 100644 --- a/shimatta_kenkyusho/static/js/kenyusho-api-v1.js +++ b/shimatta_kenkyusho/static/js/kenyusho-api-v1.js @@ -35,4 +35,20 @@ function api_search_component(search, onSuccess, onFail) { function api_get_component_from_id(id, onSuccess, onFail) { return api_ajax_request_without_send('GET', api_urls_v1['component-list']+`?search=${encodeURIComponent(id)}`, function(method, url, json) {onSuccess(json.results[0]);}, onFail); +} + +function api_search_component_types(search, onSuccess, onFail) { + return api_ajax_request_without_send('GET', api_urls_v1['component-type-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail); +} + +function api_search_package(search, onSuccess, onFail) { + return api_ajax_request_without_send('GET', api_urls_v1['package-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail); +} + +function api_search_manufacturer(search, onSuccess, onFail) { + return api_ajax_request_without_send('GET', api_urls_v1['manufacturer-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail); +} + +function api_search_distributor(search, onSuccess, onFail) { + return api_ajax_request_without_send('GET', api_urls_v1['distributor-list']+`?search=${encodeURIComponent(search)}`, function(method, url, json) {onSuccess(json);}, onFail); } \ No newline at end of file diff --git a/shimatta_kenkyusho/templates/base.html b/shimatta_kenkyusho/templates/base.html index 7253335..5f6b3cf 100644 --- a/shimatta_kenkyusho/templates/base.html +++ b/shimatta_kenkyusho/templates/base.html @@ -8,6 +8,9 @@ {{ base.title }} + {% block customhead %} {% endblock customhead %} @@ -66,6 +69,9 @@ 'package-list': '{% url 'package-list' %}', 'stock-list': '{% url 'stock-list' %}', 'distributor-list': '{% url 'distributor-list' %}', + 'manufacturer-list': '{% url 'manufacturer-list' %}', + 'component-type-list': '{% url 'componenttype-list' %}', + 'component-parameter-type-list': '{% url 'componentparametertype-list' %}', }; diff --git a/shimatta_kenkyusho/templates/parts/components-detail.html b/shimatta_kenkyusho/templates/parts/components-detail.html index 44d2a3d..0b48d59 100644 --- a/shimatta_kenkyusho/templates/parts/components-detail.html +++ b/shimatta_kenkyusho/templates/parts/components-detail.html @@ -36,17 +36,37 @@ - {{component.name}} - {% if component.package %}{{component.package.name}}{% endif %} - {% if component.manufacturer %}{{component.manufacturer.name}}{% endif %} - {% if component.component_type %}{{component.component_type.class_name}}{% endif %} - {{component.get_total_amount}} + + {{component.name}} + + + {% if component.package %} + {{component.package.name}} + {% if component.package.image %} + + {% endif %} + {% endif %} + + + {% if component.manufacturer %} + {{component.manufacturer.name}} + {% if component.manufacturer.image %} + + {% endif %} + {% endif %} + + + {% if component.component_type %}{{component.component_type.class_name}}{% endif %} + + + {{component.get_total_amount}} +

Description

{% if component.description %} - {{component.description}} + {{component.description|linebreaks}} {% else %} {% endif %} -{% include 'parts/modals/edit-component-modal.html' with heading="Edit "|add:component.name edit_form=edit_form %} +{% include 'parts/modals/edit-component-modal.html' with heading="Edit "|add:component.name form=edit_form %} {% endblock content %} {% block custom_scripts %} diff --git a/shimatta_kenkyusho/templates/parts/modals/edit-component-modal.html b/shimatta_kenkyusho/templates/parts/modals/edit-component-modal.html index e5fab77..c48d910 100644 --- a/shimatta_kenkyusho/templates/parts/modals/edit-component-modal.html +++ b/shimatta_kenkyusho/templates/parts/modals/edit-component-modal.html @@ -1,9 +1,10 @@ {% comment "" %} Needs following context: - heading -- edit_form EditComponentForm +- form EditComponentForm {% endcomment %} +{% load static %} {% load crispy_forms_tags %} - \ No newline at end of file + + + \ No newline at end of file diff --git a/shimatta_kenkyusho/templates/parts/stocks-detail.html b/shimatta_kenkyusho/templates/parts/stocks-detail.html index 3b81a22..b159b83 100644 --- a/shimatta_kenkyusho/templates/parts/stocks-detail.html +++ b/shimatta_kenkyusho/templates/parts/stocks-detail.html @@ -168,9 +168,5 @@ function(search, autocomplete_obj) { }, function(){}); }); - - {% endblock custom_scripts %}