diff --git a/.gitignore b/.gitignore index 3866c30c95..7b9522c3b7 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,8 @@ docs/_build # Local static and media file storage (only when running in development mode) InvenTree/media InvenTree/static +media +static # Local config file config.yaml diff --git a/InvenTree/InvenTree/static/css/inventree.css b/InvenTree/InvenTree/static/css/inventree.css index 321d9e1cf4..2f54a50219 100644 --- a/InvenTree/InvenTree/static/css/inventree.css +++ b/InvenTree/InvenTree/static/css/inventree.css @@ -88,6 +88,12 @@ width: 100%; } +.basecurrency { + color: #050; + font-style: italic; + font-weight: bold; +} + .bomselect { max-width: 250px; } @@ -198,6 +204,28 @@ margin-bottom: 20px; } +.settings-container { + width: 90%; + padding: 15px; +} + +.settings-nav { + height: 100%; + width: 160px; + position: fixed; + z-index: 1; + //top: 0; + //left: 0; + overflow-x: hidden; + padding-top: 20px; + padding-right: 25px; +} + +.settings-content { + margin-left: 175px; + padding: 0px 10px; +} + .breadcrump { margin-bottom: 5px; } diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 11f4a4eda5..d811c0165e 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -14,11 +14,13 @@ from company.urls import company_urls from company.urls import supplier_part_urls from company.urls import price_break_urls +from common.urls import common_urls from part.urls import part_urls from stock.urls import stock_urls from build.urls import build_urls from order.urls import order_urls +from common.api import common_api_urls from part.api import part_api_urls, bom_api_urls from company.api import company_api_urls from stock.api import stock_api_urls @@ -39,6 +41,7 @@ from users.urls import user_urls admin.site.site_header = "InvenTree Admin" apipatterns = [ + url(r'^common/', include(common_api_urls)), url(r'^part/', include(part_api_urls)), url(r'^bom/', include(bom_api_urls)), url(r'^company/', include(company_api_urls)), @@ -53,11 +56,23 @@ apipatterns = [ url(r'^$', InfoView.as_view(), name='inventree-info'), ] +settings_urls = [ + + url(r'^user/?', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings-user'), + url(r'^currency/?', SettingsView.as_view(template_name='InvenTree/settings/currency.html'), name='settings-currency'), + url(r'^part/?', SettingsView.as_view(template_name='InvenTree/settings/part.html'), name='settings-part'), + + # Catch any other urls + url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/user.html'), name='settings'), +] + urlpatterns = [ url(r'^part/', include(part_urls)), url(r'^supplier-part/', include(supplier_part_urls)), url(r'^price-break/', include(price_break_urls)), + url(r'^common/', include(common_urls)), + url(r'^stock/', include(stock_urls)), url(r'^company/', include(company_urls)), @@ -70,7 +85,7 @@ urlpatterns = [ url(r'^login/', auth_views.LoginView.as_view(), name='login'), url(r'^logout/', auth_views.LogoutView.as_view(template_name='registration/logout.html'), name='logout'), - url(r'^settings/', SettingsView.as_view(), name='settings'), + url(r'^settings/', include(settings_urls)), url(r'^edit-user/', EditUserView.as_view(), name='edit-user'), url(r'^set-password/', SetPasswordView.as_view(), name='set-password'), diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py new file mode 100644 index 0000000000..e1a9a0e3f0 --- /dev/null +++ b/InvenTree/common/api.py @@ -0,0 +1,39 @@ +""" +Provides a JSON API for common components. +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from rest_framework import permissions, generics, filters + +from django.conf.urls import url + +from .models import Currency +from .serializers import CurrencySerializer + + +class CurrencyList(generics.ListCreateAPIView): + """ API endpoint for accessing a list of Currency objects. + + - GET: Return a list of Currencies + - POST: Create a new currency + """ + + queryset = Currency.objects.all() + serializer_class = CurrencySerializer + + permission_classes = [ + permissions.IsAuthenticated, + ] + + filter_backends = [ + filters.OrderingFilter, + ] + + ordering_fields = ['suffix', 'value'] + + +common_api_urls = [ + url(r'^currency/?$', CurrencyList.as_view(), name='api-currency-list'), +] diff --git a/InvenTree/common/forms.py b/InvenTree/common/forms.py new file mode 100644 index 0000000000..00e6c15c7f --- /dev/null +++ b/InvenTree/common/forms.py @@ -0,0 +1,24 @@ +""" +Django forms for interacting with common objects +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from InvenTree.forms import HelperForm + +from .models import Currency + + +class CurrencyEditForm(HelperForm): + """ Form for creating / editing a currency object """ + + class Meta: + model = Currency + fields = [ + 'symbol', + 'suffix', + 'description', + 'value', + 'base' + ] diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py new file mode 100644 index 0000000000..73b4da8adf --- /dev/null +++ b/InvenTree/common/serializers.py @@ -0,0 +1,22 @@ +""" +JSON serializers for common components +""" + +from .models import Currency + +from InvenTree.serializers import InvenTreeModelSerializer + + +class CurrencySerializer(InvenTreeModelSerializer): + """ Serializer for Currency object """ + + class Meta: + model = Currency + fields = [ + 'pk', + 'symbol', + 'suffix', + 'description', + 'value', + 'base' + ] diff --git a/InvenTree/common/templates/common/delete_currency.html b/InvenTree/common/templates/common/delete_currency.html new file mode 100644 index 0000000000..9dfa320668 --- /dev/null +++ b/InvenTree/common/templates/common/delete_currency.html @@ -0,0 +1,7 @@ +{% extends "modal_delete_form.html" %} + +{% block pre_form_content %} + +Are you sure you wish to delete this currency? + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/common/urls.py b/InvenTree/common/urls.py new file mode 100644 index 0000000000..b5d6deadde --- /dev/null +++ b/InvenTree/common/urls.py @@ -0,0 +1,18 @@ +""" +URL lookup for common views +""" + +from django.conf.urls import url, include + +from . import views + +currency_urls = [ + url(r'^new/', views.CurrencyCreate.as_view(), name='currency-create'), + + url(r'^(?P\d+)/edit/', views.CurrencyEdit.as_view(), name='currency-edit'), + url(r'^(?P\d+)/delete/', views.CurrencyDelete.as_view(), name='currency-delete'), +] + +common_urls = [ + url(r'currency/', include(currency_urls)), +] diff --git a/InvenTree/common/views.py b/InvenTree/common/views.py index 60f00ef0ef..9e72818a36 100644 --- a/InvenTree/common/views.py +++ b/InvenTree/common/views.py @@ -1 +1,35 @@ -# Create your views here. +""" +Django views for interacting with common models +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from InvenTree.views import AjaxCreateView, AjaxUpdateView, AjaxDeleteView + +from . import models +from . import forms + + +class CurrencyCreate(AjaxCreateView): + """ View for creating a new Currency object """ + + model = models.Currency + form_class = forms.CurrencyEditForm + ajax_form_title = 'Create new Currency' + + +class CurrencyEdit(AjaxUpdateView): + """ View for editing an existing Currency object """ + + model = models.Currency + form_class = forms.CurrencyEditForm + ajax_form_title = 'Edit Currency' + + +class CurrencyDelete(AjaxDeleteView): + """ View for deleting an existing Currency object """ + + model = models.Currency + ajax_form_title = 'Delete Currency' + ajax_template_name = "common/delete_currency.html" diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index e9c6d32f0d..75c8f3a60f 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -21,10 +21,12 @@ from django.urls import reverse import os from .models import Part, PartCategory, BomItem, PartStar +from .models import PartParameter, PartParameterTemplate from .serializers import PartSerializer, BomItemSerializer from .serializers import CategorySerializer from .serializers import PartStarSerializer +from .serializers import PartParameterSerializer, PartParameterTemplateSerializer from InvenTree.views import TreeSerializer from InvenTree.helpers import str2bool @@ -261,6 +263,53 @@ class PartStarList(generics.ListCreateAPIView): ] +class PartParameterTemplateList(generics.ListCreateAPIView): + """ API endpoint for accessing a list of PartParameterTemplate objects. + + - GET: Return list of PartParameterTemplate objects + - POST: Create a new PartParameterTemplate object + """ + + queryset = PartParameterTemplate.objects.all() + serializer_class = PartParameterTemplateSerializer + + permission_classes = [ + permissions.IsAuthenticated, + ] + + filter_backends = [ + filters.OrderingFilter, + ] + + filter_fields = [ + 'name', + ] + + +class PartParameterList(generics.ListCreateAPIView): + """ API endpoint for accessing a list of PartParameter objects + + - GET: Return list of PartParameter objects + - POST: Create a new PartParameter object + """ + + queryset = PartParameter.objects.all() + serializer_class = PartParameterSerializer + + permission_classes = [ + permissions.IsAuthenticated, + ] + + filter_backends = [ + DjangoFilterBackend + ] + + filter_fields = [ + 'part', + 'template', + ] + + class BomList(generics.ListCreateAPIView): """ API endpoint for accessing a list of BomItem objects. @@ -362,12 +411,18 @@ part_star_api_urls = [ url(r'^.*$', PartStarList.as_view(), name='api-part-star-list'), ] +part_param_api_urls = [ + url(r'^template/$', PartParameterTemplateList.as_view(), name='api-part-param-template-list'), + + url(r'^.*$', PartParameterList.as_view(), name='api-part-param-list'), +] part_api_urls = [ url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'), url(r'^category/', include(cat_api_urls)), url(r'^star/', include(part_star_api_urls)), + url(r'^parameter/', include(part_param_api_urls)), url(r'^(?P\d+)/?', PartDetail.as_view(), name='api-part-detail'), diff --git a/InvenTree/part/migrations/0018_auto_20190907_0941.py b/InvenTree/part/migrations/0018_auto_20190907_0941.py new file mode 100644 index 0000000000..539759146b --- /dev/null +++ b/InvenTree/part/migrations/0018_auto_20190907_0941.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.4 on 2019-09-07 09:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0017_bomitem_checksum'), + ] + + operations = [ + migrations.AlterField( + model_name='partparametertemplate', + name='name', + field=models.CharField(help_text='Parameter Name', max_length=100, unique=True), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index b13b6632c1..4590a062a8 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -1068,7 +1068,7 @@ class PartParameterTemplate(models.Model): """ Return the number of instances of this Parameter Template """ return self.instances.count() - name = models.CharField(max_length=100, help_text='Parameter Name') + name = models.CharField(max_length=100, help_text='Parameter Name', unique=True) units = models.CharField(max_length=25, help_text='Parameter Units', blank=True) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index a8d0df5954..bdeead670d 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -8,6 +8,7 @@ from .models import Part, PartStar from .models import PartCategory from .models import BomItem +from .models import PartParameter, PartParameterTemplate from InvenTree.serializers import InvenTreeModelSerializer @@ -174,3 +175,28 @@ class BomItemSerializer(InvenTreeModelSerializer): 'note', 'validated', ] + + +class PartParameterSerializer(InvenTreeModelSerializer): + """ JSON serializers for the PartParameter model """ + + class Meta: + model = PartParameter + fields = [ + 'pk', + 'part', + 'template', + 'data' + ] + + +class PartParameterTemplateSerializer(InvenTreeModelSerializer): + """ JSON serializer for the PartParameterTemplate model """ + + class Meta: + model = PartParameterTemplate + fields = [ + 'pk', + 'name', + 'units', + ] diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index d1fd1d59fc..bbad7634c5 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -21,6 +21,8 @@ part_attachment_urls = [ part_parameter_urls = [ url('^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'), + url('^template/(?P\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'), + url('^template/(?P\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='part-param-template-edit'), url('^new/', views.PartParameterCreate.as_view(), name='part-param-create'), url('^(?P\d+)/edit/', views.PartParameterEdit.as_view(), name='part-param-edit'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 989e2c61f0..dc2697ee39 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1446,6 +1446,21 @@ class PartParameterTemplateCreate(AjaxCreateView): ajax_form_title = 'Create Part Parameter Template' +class PartParameterTemplateEdit(AjaxUpdateView): + """ View for editing a PartParameterTemplate """ + + model = PartParameterTemplate + form_class = part_forms.EditPartParameterTemplateForm + ajax_form_title = 'Edit Part Parameter Template' + + +class PartParameterTemplateDelete(AjaxDeleteView): + """ View for deleting an existing PartParameterTemplate """ + + model = PartParameterTemplate + ajax_form_title = "Delete Part Parameter Template" + + class PartParameterCreate(AjaxCreateView): """ View for creating a new PartParameter """ diff --git a/InvenTree/templates/InvenTree/settings/currency.html b/InvenTree/templates/InvenTree/settings/currency.html new file mode 100644 index 0000000000..2c7b3dbccb --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/currency.html @@ -0,0 +1,108 @@ +{% extends "InvenTree/settings/settings.html" %} + +{% block tabs %} +{% include "InvenTree/settings/tabs.html" with tab='currency' %} +{% endblock %} + +{% block settings %} + +

Currencies

+ +
+ +
+ + +
+{% endblock %} + +{% block js_ready %} +{{ block.super }} + + $("#currency-table").bootstrapTable({ + url: "{% url 'api-currency-list' %}", + queryParams: { + ordering: 'suffix' + }, + sortable: true, + search: true, + pagination: true, + pageSize: 25, + formatNoMatches: function() { return "No currencies found"; }, + rowStyle: function(row, index) { + if (row.base) { + return {classes: 'basecurrency'}; + } else { + return {}; + } + }, + columns: [ + { + field: 'pk', + title: 'ID', + visible: false, + }, + { + field: 'symbol', + title: 'Symbol', + }, + { + field: 'suffix', + title: 'Currency', + sortable: true, + }, + { + field: 'description', + title: 'Description', + sortable: true, + }, + { + field: 'value', + title: 'Value', + sortable: true, + }, + { + formatter: function(value, row, index, field) { + + var bEdit = ""; + var bDel = ""; + + var html = "
" + bEdit + bDel + "
"; + + return html; + } + } + ] + }); + + $("#currency-table").on('click', '.cur-edit', function() { + var button = $(this); + var url = "/common/currency/" + button.attr('pk') + "/edit/"; + + launchModalForm(url, { + success: function() { + $("#currency-table").bootstrapTable('refresh'); + }, + }); + }); + + $("#currency-table").on('click', '.cur-delete', function() { + var button = $(this); + var url = "/common/currency/" + button.attr('pk') + "/delete/"; + + launchModalForm(url, { + success: function() { + $("#currency-table").bootstrapTable('refresh'); + }, + }); + }); + + $("#new-currency").click(function() { + launchModalForm("{% url 'currency-create' %}", { + success: function() { + $("#currency-table").bootstrapTable('refresh'); + }, + }); + }); + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/part.html b/InvenTree/templates/InvenTree/settings/part.html new file mode 100644 index 0000000000..87535c7db1 --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/part.html @@ -0,0 +1,93 @@ +{% extends "InvenTree/settings/settings.html" %} + +{% block tabs %} +{% include "InvenTree/settings/tabs.html" with tab='part' %} +{% endblock %} + +{% block settings %} +

Part Parameter Templates

+ +
+ +
+ + +
+ +{% endblock %} + +{% block js_ready %} +{{ block.super }} + + $("#param-table").bootstrapTable({ + url: "{% url 'api-part-param-template-list' %}", + queryParams: { + ordering: 'name', + }, + sortable: true, + search: true, + pagination: true, + pageSize: 25, + formatNoMatches: function() { return "No part parameter templates found"; }, + columns: [ + { + field: 'pk', + title: 'ID', + visible: false, + }, + { + field: 'name', + title: 'Name', + sortable: 'true', + }, + { + field: 'units', + title: 'Units', + sortable: 'true', + }, + { + formatter: function(value, row, index, field) { + var bEdit = ""; + var bDel = ""; + + var html = "
" + bEdit + bDel + "
"; + + return html; + } + } + ] + }); + + $("#new-param").click(function() { + launchModalForm("{% url 'part-param-template-create' %}", { + success: function() { + $("#param-table").bootstrapTable('refresh'); + }, + }); + }); + + $("#param-table").on('click', '.template-edit', function() { + var button = $(this); + + var url = "/part/parameter/template/" + button.attr('pk') + "/edit/"; + + launchModalForm(url, { + success: function() { + $("#param-table").bootstrapTable('refresh'); + } + }); + }); + + $("#param-table").on('click', '.template-delete', function() { + var button = $(this); + + var url = "/part/parameter/template/" + button.attr('pk') + "/delete/"; + + launchModalForm(url, { + success: function() { + $("#param-table").bootstrapTable('refresh'); + } + }); + }); + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings/settings.html b/InvenTree/templates/InvenTree/settings/settings.html new file mode 100644 index 0000000000..dab7ec277b --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/settings.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% load static %} + +{% block page_title %} +InvenTree | Settings +{% endblock %} + +{% block content %} +
+ +

InvenTree Settings

+
+ +
+ {% block tabs %} + {% include "InvenTree/settings/tabs.html" %} + {% endblock %} +
+ +
+ {% block settings %} + {% endblock %} +
+ +
+ +{% endblock %} + +{% block js_load %} +{{ block.super }} +{% endblock %} diff --git a/InvenTree/templates/InvenTree/settings/tabs.html b/InvenTree/templates/InvenTree/settings/tabs.html new file mode 100644 index 0000000000..78d15dbcfe --- /dev/null +++ b/InvenTree/templates/InvenTree/settings/tabs.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings.html b/InvenTree/templates/InvenTree/settings/user.html similarity index 85% rename from InvenTree/templates/InvenTree/settings.html rename to InvenTree/templates/InvenTree/settings/user.html index 2a2bbcc144..cea588467b 100644 --- a/InvenTree/templates/InvenTree/settings.html +++ b/InvenTree/templates/InvenTree/settings/user.html @@ -1,12 +1,10 @@ -{% extends "base.html" %} +{% extends "InvenTree/settings/settings.html" %} -{% block page_title %} -InvenTree | Settings +{% block tabs %} +{% include "InvenTree/settings/tabs.html" with tab='user' %} {% endblock %} -{% block content %} -

InvenTree Settings

-
+{% block settings %}
@@ -18,8 +16,7 @@ InvenTree | Settings
Set Password
- - + @@ -38,10 +35,6 @@ InvenTree | Settings {% endblock %} -{% block js_load %} -{{ block.super }} -{% endblock %} - {% block js_ready %} {{ block.super }}