Merge remote-tracking branch 'inventree/master'

This commit is contained in:
Oliver Walters 2019-09-07 20:37:30 +10:00
commit 7be1edd896
21 changed files with 575 additions and 16 deletions

2
.gitignore vendored
View File

@ -34,6 +34,8 @@ docs/_build
# Local static and media file storage (only when running in development mode) # Local static and media file storage (only when running in development mode)
InvenTree/media InvenTree/media
InvenTree/static InvenTree/static
media
static
# Local config file # Local config file
config.yaml config.yaml

View File

@ -88,6 +88,12 @@
width: 100%; width: 100%;
} }
.basecurrency {
color: #050;
font-style: italic;
font-weight: bold;
}
.bomselect { .bomselect {
max-width: 250px; max-width: 250px;
} }
@ -198,6 +204,28 @@
margin-bottom: 20px; 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 { .breadcrump {
margin-bottom: 5px; margin-bottom: 5px;
} }

View File

@ -14,11 +14,13 @@ from company.urls import company_urls
from company.urls import supplier_part_urls from company.urls import supplier_part_urls
from company.urls import price_break_urls from company.urls import price_break_urls
from common.urls import common_urls
from part.urls import part_urls from part.urls import part_urls
from stock.urls import stock_urls from stock.urls import stock_urls
from build.urls import build_urls from build.urls import build_urls
from order.urls import order_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 part.api import part_api_urls, bom_api_urls
from company.api import company_api_urls from company.api import company_api_urls
from stock.api import stock_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" admin.site.site_header = "InvenTree Admin"
apipatterns = [ apipatterns = [
url(r'^common/', include(common_api_urls)),
url(r'^part/', include(part_api_urls)), url(r'^part/', include(part_api_urls)),
url(r'^bom/', include(bom_api_urls)), url(r'^bom/', include(bom_api_urls)),
url(r'^company/', include(company_api_urls)), url(r'^company/', include(company_api_urls)),
@ -53,11 +56,23 @@ apipatterns = [
url(r'^$', InfoView.as_view(), name='inventree-info'), 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 = [ urlpatterns = [
url(r'^part/', include(part_urls)), url(r'^part/', include(part_urls)),
url(r'^supplier-part/', include(supplier_part_urls)), url(r'^supplier-part/', include(supplier_part_urls)),
url(r'^price-break/', include(price_break_urls)), url(r'^price-break/', include(price_break_urls)),
url(r'^common/', include(common_urls)),
url(r'^stock/', include(stock_urls)), url(r'^stock/', include(stock_urls)),
url(r'^company/', include(company_urls)), url(r'^company/', include(company_urls)),
@ -70,7 +85,7 @@ urlpatterns = [
url(r'^login/', auth_views.LoginView.as_view(), name='login'), 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'^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'^edit-user/', EditUserView.as_view(), name='edit-user'),
url(r'^set-password/', SetPasswordView.as_view(), name='set-password'), url(r'^set-password/', SetPasswordView.as_view(), name='set-password'),

39
InvenTree/common/api.py Normal file
View File

@ -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'),
]

24
InvenTree/common/forms.py Normal file
View File

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

View File

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

View File

@ -0,0 +1,7 @@
{% extends "modal_delete_form.html" %}
{% block pre_form_content %}
Are you sure you wish to delete this currency?
{% endblock %}

18
InvenTree/common/urls.py Normal file
View File

@ -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<pk>\d+)/edit/', views.CurrencyEdit.as_view(), name='currency-edit'),
url(r'^(?P<pk>\d+)/delete/', views.CurrencyDelete.as_view(), name='currency-delete'),
]
common_urls = [
url(r'currency/', include(currency_urls)),
]

View File

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

View File

@ -21,10 +21,12 @@ from django.urls import reverse
import os import os
from .models import Part, PartCategory, BomItem, PartStar from .models import Part, PartCategory, BomItem, PartStar
from .models import PartParameter, PartParameterTemplate
from .serializers import PartSerializer, BomItemSerializer from .serializers import PartSerializer, BomItemSerializer
from .serializers import CategorySerializer from .serializers import CategorySerializer
from .serializers import PartStarSerializer from .serializers import PartStarSerializer
from .serializers import PartParameterSerializer, PartParameterTemplateSerializer
from InvenTree.views import TreeSerializer from InvenTree.views import TreeSerializer
from InvenTree.helpers import str2bool 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): class BomList(generics.ListCreateAPIView):
""" API endpoint for accessing a list of BomItem objects. """ 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'), 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 = [ part_api_urls = [
url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'), url(r'^tree/?', PartCategoryTree.as_view(), name='api-part-tree'),
url(r'^category/', include(cat_api_urls)), url(r'^category/', include(cat_api_urls)),
url(r'^star/', include(part_star_api_urls)), url(r'^star/', include(part_star_api_urls)),
url(r'^parameter/', include(part_param_api_urls)),
url(r'^(?P<pk>\d+)/?', PartDetail.as_view(), name='api-part-detail'), url(r'^(?P<pk>\d+)/?', PartDetail.as_view(), name='api-part-detail'),

View File

@ -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),
),
]

View File

@ -1068,7 +1068,7 @@ class PartParameterTemplate(models.Model):
""" Return the number of instances of this Parameter Template """ """ Return the number of instances of this Parameter Template """
return self.instances.count() 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) units = models.CharField(max_length=25, help_text='Parameter Units', blank=True)

View File

@ -8,6 +8,7 @@ from .models import Part, PartStar
from .models import PartCategory from .models import PartCategory
from .models import BomItem from .models import BomItem
from .models import PartParameter, PartParameterTemplate
from InvenTree.serializers import InvenTreeModelSerializer from InvenTree.serializers import InvenTreeModelSerializer
@ -174,3 +175,28 @@ class BomItemSerializer(InvenTreeModelSerializer):
'note', 'note',
'validated', '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',
]

View File

@ -21,6 +21,8 @@ part_attachment_urls = [
part_parameter_urls = [ part_parameter_urls = [
url('^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'), url('^template/new/', views.PartParameterTemplateCreate.as_view(), name='part-param-template-create'),
url('^template/(?P<pk>\d+)/edit/', views.PartParameterTemplateEdit.as_view(), name='part-param-template-edit'),
url('^template/(?P<pk>\d+)/delete/', views.PartParameterTemplateDelete.as_view(), name='part-param-template-edit'),
url('^new/', views.PartParameterCreate.as_view(), name='part-param-create'), url('^new/', views.PartParameterCreate.as_view(), name='part-param-create'),
url('^(?P<pk>\d+)/edit/', views.PartParameterEdit.as_view(), name='part-param-edit'), url('^(?P<pk>\d+)/edit/', views.PartParameterEdit.as_view(), name='part-param-edit'),

View File

@ -1446,6 +1446,21 @@ class PartParameterTemplateCreate(AjaxCreateView):
ajax_form_title = 'Create Part Parameter Template' 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): class PartParameterCreate(AjaxCreateView):
""" View for creating a new PartParameter """ """ View for creating a new PartParameter """

View File

@ -0,0 +1,108 @@
{% extends "InvenTree/settings/settings.html" %}
{% block tabs %}
{% include "InvenTree/settings/tabs.html" with tab='currency' %}
{% endblock %}
{% block settings %}
<h4>Currencies</h4>
<div id='currency-buttons'>
<button class='btn btn-success' id='new-currency'>New Currency</button>
</div>
<table class='table table-striped table-condensed' id='currency-table' data-toolbar='#currency-buttons'>
</table>
{% 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 = "<button title='Edit Currency' class='cur-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='glyphicon glyphicon-edit'></span></button>";
var bDel = "<button title='Delete Currency' class='cur-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='glyphicon glyphicon-trash'></span></button>";
var html = "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
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 %}

View File

@ -0,0 +1,93 @@
{% extends "InvenTree/settings/settings.html" %}
{% block tabs %}
{% include "InvenTree/settings/tabs.html" with tab='part' %}
{% endblock %}
{% block settings %}
<h4>Part Parameter Templates</h4>
<div id='param-buttons'>
<button class='btn btn-success' id='new-param'>New Parameter</button>
</div>
<table class='table table-striped table-condensed' id='param-table' data-toolbar='#param-buttons'>
</table>
{% 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 = "<button title='Edit Template' class='template-edit btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='glyphicon glyphicon-edit'></span></button>";
var bDel = "<button title='Delete Template' class='template-delete btn btn-default btn-glyph' type='button' pk='" + row.pk + "'><span class='glyphicon glyphicon-trash'></span></button>";
var html = "<div class='btn-group' role='group'>" + bEdit + bDel + "</div>";
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 %}

View File

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% load static %}
{% block page_title %}
InvenTree | Settings
{% endblock %}
{% block content %}
<div class='settings-container'>
<h3>InvenTree Settings</h3>
<hr>
<div class='settings-nav'>
{% block tabs %}
{% include "InvenTree/settings/tabs.html" %}
{% endblock %}
</div>
<div class='settings-content'>
{% block settings %}
{% endblock %}
</div>
</div>
{% endblock %}
{% block js_load %}
{{ block.super }}
{% endblock %}

View File

@ -0,0 +1,11 @@
<ul class='nav nav-pills nav-stacked'>
<li{% ifequal tab 'user' %} class='active'{% endifequal %}>
<a href="{% url 'settings-user' %}"><span class='glyphicon glyphicon-user'></span> User</a>
</li>
<li{% ifequal tab 'currency' %} class='active'{% endifequal %}>
<a href="{% url 'settings-currency' %}"><span class='glyphicon glyphicon-usd'></span> Currency</a>
</li>
<li{% ifequal tab 'part' %} class='active'{% endifequal %}>
<a href="{% url 'settings-part' %}"><span class='glyphicon glyphicon-briefcase'></span> Part</a>
</li>
</ul>

View File

@ -1,12 +1,10 @@
{% extends "base.html" %} {% extends "InvenTree/settings/settings.html" %}
{% block page_title %} {% block tabs %}
InvenTree | Settings {% include "InvenTree/settings/tabs.html" with tab='user' %}
{% endblock %} {% endblock %}
{% block content %} {% block settings %}
<h3>InvenTree Settings</h3>
<hr>
<div class='row'> <div class='row'>
<div class='col-sm-6'> <div class='col-sm-6'>
@ -18,8 +16,7 @@ InvenTree | Settings
<div class='btn btn-primary' type='button' id='edit-password' title='Change Password'>Set Password</div> <div class='btn btn-primary' type='button' id='edit-password' title='Change Password'>Set Password</div>
</div> </div>
</div> </div>
</div> </div>
<table class='table table-striped table-condensed'> <table class='table table-striped table-condensed'>
<tr> <tr>
@ -38,10 +35,6 @@ InvenTree | Settings
{% endblock %} {% endblock %}
{% block js_load %}
{{ block.super }}
{% endblock %}
{% block js_ready %} {% block js_ready %}
{{ block.super }} {{ block.super }}

View File

@ -1,10 +1,12 @@
from rest_framework import generics, permissions from rest_framework import generics, permissions
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from .serializers import UserSerializer from .serializers import UserSerializer
from rest_framework.authtoken.views import ObtainAuthToken from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status
class UserDetail(generics.RetrieveAPIView): class UserDetail(generics.RetrieveAPIView):
@ -27,15 +29,30 @@ class GetAuthToken(ObtainAuthToken):
""" Return authentication token for an authenticated user. """ """ Return authentication token for an authenticated user. """
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
return self.login(request)
def delete(self, request):
return self.logout(request)
def login(self, request):
serializer = self.serializer_class(data=request.data, serializer = self.serializer_class(data=request.data,
context={'request': request}) context={'request': request})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user'] user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user) token, created = Token.objects.get_or_create(user=user)
return Response({ return Response({
'token': token.key, 'token': token.key,
'pk': user.pk, 'pk': user.pk,
'username': user.username, 'username': user.username,
'email': user.email 'email': user.email
}) })
def logout(self, request):
try:
request.user.auth_token.delete()
return Response({"success": "Successfully logged out."},
status=status.HTTP_202_ACCEPTED)
except (AttributeError, ObjectDoesNotExist):
return Response({"error": "Bad request"},
status=status.HTTP_400_BAD_REQUEST)