mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master'
This commit is contained in:
commit
e3a8bb23c1
@ -7,6 +7,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from crispy_forms.helper import FormHelper
|
from crispy_forms.helper import FormHelper
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
class HelperForm(forms.ModelForm):
|
class HelperForm(forms.ModelForm):
|
||||||
@ -33,3 +34,42 @@ class DeleteForm(forms.Form):
|
|||||||
fields = [
|
fields = [
|
||||||
'confirm_delete'
|
'confirm_delete'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class EditUserForm(HelperForm):
|
||||||
|
""" Form for editing user information
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = [
|
||||||
|
'first_name',
|
||||||
|
'last_name',
|
||||||
|
'email'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class SetPasswordForm(HelperForm):
|
||||||
|
""" Form for setting user password
|
||||||
|
"""
|
||||||
|
|
||||||
|
enter_password = forms.CharField(max_length=100,
|
||||||
|
min_length=8,
|
||||||
|
required=True,
|
||||||
|
initial='',
|
||||||
|
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
|
||||||
|
help_text='Enter new password')
|
||||||
|
|
||||||
|
confirm_password = forms.CharField(max_length=100,
|
||||||
|
min_length=8,
|
||||||
|
required=True,
|
||||||
|
initial='',
|
||||||
|
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
|
||||||
|
help_text='Confirm new password')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = [
|
||||||
|
'enter_password',
|
||||||
|
'confirm_password'
|
||||||
|
]
|
||||||
|
@ -30,7 +30,7 @@ from django.conf.urls.static import static
|
|||||||
from django.views.generic.base import RedirectView
|
from django.views.generic.base import RedirectView
|
||||||
from rest_framework.documentation import include_docs_urls
|
from rest_framework.documentation import include_docs_urls
|
||||||
|
|
||||||
from .views import IndexView, SearchView
|
from .views import IndexView, SearchView, SettingsView, EditUserView, SetPasswordView
|
||||||
|
|
||||||
from users.urls import user_urls
|
from users.urls import user_urls
|
||||||
|
|
||||||
@ -62,6 +62,11 @@ 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'^edit-user/', EditUserView.as_view(), name='edit-user'),
|
||||||
|
url(r'^set-password/', SetPasswordView.as_view(), name='set-password'),
|
||||||
|
|
||||||
url(r'^admin/', admin.site.urls, name='inventree-admin'),
|
url(r'^admin/', admin.site.urls, name='inventree-admin'),
|
||||||
|
|
||||||
url(r'^qr_code/', include(qr_code_urls, namespace='qr_code')),
|
url(r'^qr_code/', include(qr_code_urls, namespace='qr_code')),
|
||||||
|
@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
def validate_part_name(value):
|
def validate_part_name(value):
|
||||||
# Prevent some illegal characters in part names
|
# Prevent some illegal characters in part names
|
||||||
for c in ['/', '\\', '|', '#', '$']:
|
for c in ['|', '#', '$']:
|
||||||
if c in str(value):
|
if c in str(value):
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
_('Invalid character in part name')
|
_('Invalid character in part name')
|
||||||
|
@ -17,7 +17,7 @@ from django.views.generic.base import TemplateView
|
|||||||
|
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
from .forms import DeleteForm
|
from .forms import DeleteForm, EditUserForm, SetPasswordForm
|
||||||
from .helpers import str2bool
|
from .helpers import str2bool
|
||||||
|
|
||||||
from rest_framework import views
|
from rest_framework import views
|
||||||
@ -371,6 +371,59 @@ class AjaxDeleteView(AjaxMixin, UpdateView):
|
|||||||
return self.renderJsonResponse(request, form, data=data, context=context)
|
return self.renderJsonResponse(request, form, data=data, context=context)
|
||||||
|
|
||||||
|
|
||||||
|
class EditUserView(AjaxUpdateView):
|
||||||
|
""" View for editing user information """
|
||||||
|
|
||||||
|
ajax_template_name = "modal_form.html"
|
||||||
|
ajax_form_title = "Edit User Information"
|
||||||
|
form_class = EditUserForm
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
|
||||||
|
class SetPasswordView(AjaxUpdateView):
|
||||||
|
""" View for setting user password """
|
||||||
|
|
||||||
|
ajax_template_name = "InvenTree/password.html"
|
||||||
|
ajax_form_title = "Set Password"
|
||||||
|
form_class = SetPasswordForm
|
||||||
|
|
||||||
|
def get_object(self):
|
||||||
|
return self.request.user
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
valid = form.is_valid()
|
||||||
|
|
||||||
|
p1 = request.POST.get('enter_password', '')
|
||||||
|
p2 = request.POST.get('confirm_password', '')
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
# Passwords must match
|
||||||
|
|
||||||
|
if not p1 == p2:
|
||||||
|
error = 'Password fields must match'
|
||||||
|
form.errors['enter_password'] = [error]
|
||||||
|
form.errors['confirm_password'] = [error]
|
||||||
|
|
||||||
|
valid = False
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'form_valid': valid
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
user = self.request.user
|
||||||
|
|
||||||
|
user.set_password(p1)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, form, data=data)
|
||||||
|
|
||||||
|
|
||||||
class IndexView(TemplateView):
|
class IndexView(TemplateView):
|
||||||
""" View for InvenTree index page """
|
""" View for InvenTree index page """
|
||||||
|
|
||||||
@ -414,3 +467,10 @@ class SearchView(TemplateView):
|
|||||||
context['query'] = query
|
context['query'] = query
|
||||||
|
|
||||||
return super(TemplateView, self).render_to_response(context)
|
return super(TemplateView, self).render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsView(TemplateView):
|
||||||
|
""" View for configuring User settings
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_name = "InvenTree/settings.html"
|
||||||
|
@ -58,6 +58,18 @@ class CompleteBuildForm(HelperForm):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CancelBuildForm(HelperForm):
|
||||||
|
""" Form for cancelling a build """
|
||||||
|
|
||||||
|
confirm_cancel = forms.BooleanField(required=False, help_text='Confirm build cancellation')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Build
|
||||||
|
fields = [
|
||||||
|
'confirm_cancel'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class EditBuildItemForm(HelperForm):
|
class EditBuildItemForm(HelperForm):
|
||||||
""" Form for adding a new BuildItem to a Build """
|
""" Form for adding a new BuildItem to a Build """
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ Automatically allocate stock to this build?
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class='hover-icon'>
|
<a class='hover-icon'>
|
||||||
<img class='hover-img-thumb' src='{{ item.stock_item.part.image.url }}'>
|
<img class='hover-img-thumb' src='{% if item.stock_item.part.image %}{{ item.stock_item.part.image.url }}{% endif %}'>
|
||||||
<img class='hover-img-large' src='{{ item.stock_item.part.image.url }}'>
|
<img class='hover-img-large' src='{% if item.stock_item.part.image %}{{ item.stock_item.part.image.url }}{% endif %}'>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
{% extends "modal_form.html" %}
|
||||||
|
|
||||||
|
{% block pre_form_content %}
|
||||||
|
|
||||||
Are you sure you wish to cancel this build?
|
Are you sure you wish to cancel this build?
|
||||||
|
|
||||||
{% include "modal_csrf.html" %}
|
{% endblock %}
|
@ -5,8 +5,6 @@ Django views for interacting with Build objects
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
|
|
||||||
from django.views.generic import DetailView, ListView
|
from django.views.generic import DetailView, ListView
|
||||||
from django.forms import HiddenInput
|
from django.forms import HiddenInput
|
||||||
|
|
||||||
@ -15,7 +13,8 @@ from .models import Build, BuildItem
|
|||||||
from . import forms
|
from . import forms
|
||||||
from stock.models import StockLocation, StockItem
|
from stock.models import StockLocation, StockItem
|
||||||
|
|
||||||
from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView, AjaxDeleteView
|
from InvenTree.views import AjaxUpdateView, AjaxCreateView, AjaxDeleteView
|
||||||
|
from InvenTree.helpers import str2bool
|
||||||
|
|
||||||
|
|
||||||
class BuildIndex(ListView):
|
class BuildIndex(ListView):
|
||||||
@ -41,31 +40,41 @@ class BuildIndex(ListView):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class BuildCancel(AjaxView):
|
class BuildCancel(AjaxUpdateView):
|
||||||
""" View to cancel a Build.
|
""" View to cancel a Build.
|
||||||
Provides a cancellation information dialog
|
Provides a cancellation information dialog
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = Build
|
model = Build
|
||||||
ajax_template_name = 'build/cancel.html'
|
ajax_template_name = 'build/cancel.html'
|
||||||
ajax_form_title = 'Cancel Build'
|
ajax_form_title = 'Cancel Build'
|
||||||
context_object_name = 'build'
|
context_object_name = 'build'
|
||||||
fields = []
|
form_class = forms.CancelBuildForm
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
""" Handle POST request. Mark the build status as CANCELLED """
|
""" Handle POST request. Mark the build status as CANCELLED """
|
||||||
|
|
||||||
build = get_object_or_404(Build, pk=self.kwargs['pk'])
|
build = self.get_object()
|
||||||
|
|
||||||
|
form = self.get_form()
|
||||||
|
|
||||||
|
valid = form.is_valid()
|
||||||
|
|
||||||
|
confirm = str2bool(request.POST.get('confirm_cancel', False))
|
||||||
|
|
||||||
|
if confirm:
|
||||||
build.cancelBuild(request.user)
|
build.cancelBuild(request.user)
|
||||||
|
else:
|
||||||
|
form.errors['confirm_cancel'] = ['Confirm build cancellation']
|
||||||
|
valid = False
|
||||||
|
|
||||||
return self.renderJsonResponse(request, None)
|
data = {
|
||||||
|
'form_valid': valid,
|
||||||
def get_data(self):
|
|
||||||
""" Provide JSON context data. """
|
|
||||||
return {
|
|
||||||
'danger': 'Build was cancelled'
|
'danger': 'Build was cancelled'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return self.renderJsonResponse(request, form, data=data)
|
||||||
|
|
||||||
|
|
||||||
class BuildAutoAllocate(AjaxUpdateView):
|
class BuildAutoAllocate(AjaxUpdateView):
|
||||||
""" View to auto-allocate parts for a build.
|
""" View to auto-allocate parts for a build.
|
||||||
@ -90,7 +99,7 @@ class BuildAutoAllocate(AjaxUpdateView):
|
|||||||
context['build'] = build
|
context['build'] = build
|
||||||
context['allocations'] = build.getAutoAllocations()
|
context['allocations'] = build.getAutoAllocations()
|
||||||
except Build.DoesNotExist:
|
except Build.DoesNotExist:
|
||||||
context['error'] = 'No matching buidl found'
|
context['error'] = 'No matching build found'
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -217,7 +226,7 @@ class BuildComplete(AjaxUpdateView):
|
|||||||
|
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
|
|
||||||
confirm = request.POST.get('confirm', False)
|
confirm = str2bool(request.POST.get('confirm', False))
|
||||||
|
|
||||||
loc_id = request.POST.get('location', None)
|
loc_id = request.POST.get('location', None)
|
||||||
|
|
||||||
|
@ -157,6 +157,7 @@ class PartList(generics.ListCreateAPIView):
|
|||||||
'$name',
|
'$name',
|
||||||
'description',
|
'description',
|
||||||
'$IPN',
|
'$IPN',
|
||||||
|
'keywords',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,9 +92,10 @@ class EditPartForm(HelperForm):
|
|||||||
'confirm_creation',
|
'confirm_creation',
|
||||||
'category',
|
'category',
|
||||||
'name',
|
'name',
|
||||||
|
'IPN',
|
||||||
'variant',
|
'variant',
|
||||||
'description',
|
'description',
|
||||||
'IPN',
|
'keywords',
|
||||||
'URL',
|
'URL',
|
||||||
'default_location',
|
'default_location',
|
||||||
'default_supplier',
|
'default_supplier',
|
||||||
@ -118,7 +119,8 @@ class EditCategoryForm(HelperForm):
|
|||||||
'parent',
|
'parent',
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'default_location'
|
'default_location',
|
||||||
|
'default_keywords',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
18
InvenTree/part/migrations/0023_part_keywords.py
Normal file
18
InvenTree/part/migrations/0023_part_keywords.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-05-14 07:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0022_auto_20190512_1246'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='part',
|
||||||
|
name='keywords',
|
||||||
|
field=models.CharField(blank=True, help_text='Part keywords to improve visibility in search results', max_length=250),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-05-14 07:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('part', '0023_part_keywords'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='partcategory',
|
||||||
|
name='default_keywords',
|
||||||
|
field=models.CharField(blank=True, help_text='Default keywords for parts in this category', max_length=250),
|
||||||
|
),
|
||||||
|
]
|
@ -37,6 +37,12 @@ from company.models import Company
|
|||||||
|
|
||||||
class PartCategory(InvenTreeTree):
|
class PartCategory(InvenTreeTree):
|
||||||
""" PartCategory provides hierarchical organization of Part objects.
|
""" PartCategory provides hierarchical organization of Part objects.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
name: Name of this category
|
||||||
|
parent: Parent category
|
||||||
|
default_location: Default storage location for parts in this category or child categories
|
||||||
|
default_keywords: Default keywords for parts created in this category
|
||||||
"""
|
"""
|
||||||
|
|
||||||
default_location = models.ForeignKey(
|
default_location = models.ForeignKey(
|
||||||
@ -46,6 +52,8 @@ class PartCategory(InvenTreeTree):
|
|||||||
help_text='Default location for parts in this category'
|
help_text='Default location for parts in this category'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
default_keywords = models.CharField(blank=True, max_length=250, help_text='Default keywords for parts in this category')
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('category-detail', kwargs={'pk': self.id})
|
return reverse('category-detail', kwargs={'pk': self.id})
|
||||||
|
|
||||||
@ -179,8 +187,9 @@ class Part(models.Model):
|
|||||||
Attributes:
|
Attributes:
|
||||||
name: Brief name for this part
|
name: Brief name for this part
|
||||||
variant: Optional variant number for this part - Must be unique for the part name
|
variant: Optional variant number for this part - Must be unique for the part name
|
||||||
description: Longer form description of the part
|
|
||||||
category: The PartCategory to which this part belongs
|
category: The PartCategory to which this part belongs
|
||||||
|
description: Longer form description of the part
|
||||||
|
keywords: Optional keywords for improving part search results
|
||||||
IPN: Internal part number (optional)
|
IPN: Internal part number (optional)
|
||||||
URL: Link to an external page with more information about this part (e.g. internal Wiki)
|
URL: Link to an external page with more information about this part (e.g. internal Wiki)
|
||||||
image: Image of this part
|
image: Image of this part
|
||||||
@ -250,6 +259,8 @@ class Part(models.Model):
|
|||||||
|
|
||||||
description = models.CharField(max_length=250, blank=False, help_text='Part description')
|
description = models.CharField(max_length=250, blank=False, help_text='Part description')
|
||||||
|
|
||||||
|
keywords = models.CharField(max_length=250, blank=True, help_text='Part keywords to improve visibility in search results')
|
||||||
|
|
||||||
category = models.ForeignKey(PartCategory, related_name='parts',
|
category = models.ForeignKey(PartCategory, related_name='parts',
|
||||||
null=True, blank=True,
|
null=True, blank=True,
|
||||||
on_delete=models.DO_NOTHING,
|
on_delete=models.DO_NOTHING,
|
||||||
|
@ -62,15 +62,16 @@ class PartSerializer(serializers.ModelSerializer):
|
|||||||
fields = [
|
fields = [
|
||||||
'pk',
|
'pk',
|
||||||
'url', # Link to the part detail page
|
'url', # Link to the part detail page
|
||||||
'full_name',
|
|
||||||
'name',
|
|
||||||
'variant',
|
|
||||||
'image_url',
|
|
||||||
'IPN',
|
|
||||||
'URL', # Link to an external URL (optional)
|
|
||||||
'description',
|
|
||||||
'category',
|
'category',
|
||||||
'category_name',
|
'category_name',
|
||||||
|
'image_url',
|
||||||
|
'full_name',
|
||||||
|
'name',
|
||||||
|
'IPN',
|
||||||
|
'variant',
|
||||||
|
'description',
|
||||||
|
'keywords',
|
||||||
|
'URL',
|
||||||
'total_stock',
|
'total_stock',
|
||||||
'available_stock',
|
'available_stock',
|
||||||
'units',
|
'units',
|
||||||
|
@ -35,16 +35,22 @@
|
|||||||
<td>Part name</td>
|
<td>Part name</td>
|
||||||
<td>{{ part.full_name }}</td>
|
<td>{{ part.full_name }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Description</td>
|
|
||||||
<td>{{ part.description }}</td>
|
|
||||||
</tr>
|
|
||||||
{% if part.IPN %}
|
{% if part.IPN %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>IPN</td>
|
<td>IPN</td>
|
||||||
<td>{{ part.IPN }}</td>
|
<td>{{ part.IPN }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td>{{ part.description }}</td>
|
||||||
|
</tr>
|
||||||
|
{% if part.keywords %}
|
||||||
|
<tr>
|
||||||
|
<td>Keywords</td>
|
||||||
|
<td>{{ part.keywords }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% if part.URL %}
|
{% if part.URL %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>URL</td>
|
<td>URL</td>
|
||||||
|
@ -339,7 +339,9 @@ class PartCreate(AjaxCreateView):
|
|||||||
|
|
||||||
if self.get_category_id():
|
if self.get_category_id():
|
||||||
try:
|
try:
|
||||||
initials['category'] = PartCategory.objects.get(pk=self.get_category_id())
|
category = PartCategory.objects.get(pk=self.get_category_id())
|
||||||
|
initials['category'] = category
|
||||||
|
initials['keywords'] = category.default_keywords
|
||||||
except PartCategory.DoesNotExist:
|
except PartCategory.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -89,11 +89,11 @@ function loadBomTable(table, options) {
|
|||||||
// Part column
|
// Part column
|
||||||
cols.push(
|
cols.push(
|
||||||
{
|
{
|
||||||
field: 'sub_part_detail',
|
field: 'sub_part_detail.full_name',
|
||||||
title: 'Part',
|
title: 'Part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
return imageHoverIcon(value.image_url) + renderLink(value.full_name, value.url);
|
return imageHoverIcon(row.sub_part_detail.image_url) + renderLink(row.sub_part_detail.full_name, row.sub_part_detail.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -116,6 +116,34 @@ function loadBomTable(table, options) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!options.editable) {
|
||||||
|
cols.push(
|
||||||
|
{
|
||||||
|
field: 'sub_part_detail.available_stock',
|
||||||
|
title: 'Available',
|
||||||
|
searchable: false,
|
||||||
|
sortable: true,
|
||||||
|
formatter: function(value, row, index, field) {
|
||||||
|
var text = "";
|
||||||
|
|
||||||
|
if (row.quantity < row.sub_part_detail.available_stock)
|
||||||
|
{
|
||||||
|
text = "<span class='label label-success'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!value) {
|
||||||
|
value = 'No Stock';
|
||||||
|
}
|
||||||
|
text = "<span class='label label-warning'>" + value + "</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderLink(text, row.sub_part_detail.url + "stock/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Part notes
|
// Part notes
|
||||||
cols.push(
|
cols.push(
|
||||||
{
|
{
|
||||||
@ -137,31 +165,6 @@ function loadBomTable(table, options) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
|
||||||
cols.push(
|
|
||||||
{
|
|
||||||
field: 'sub_part_detail.available_stock',
|
|
||||||
title: 'Available',
|
|
||||||
searchable: false,
|
|
||||||
sortable: true,
|
|
||||||
formatter: function(value, row, index, field) {
|
|
||||||
var text = "";
|
|
||||||
|
|
||||||
if (row.quantity < row.sub_part_detail.available_stock)
|
|
||||||
{
|
|
||||||
text = "<span class='label label-success'>" + value + "</span>";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
text = "<span class='label label-warning'>" + value + "</span>";
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderLink(text, row.sub_part.url + "stock/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the table (bootstrap-table)
|
// Configure the table (bootstrap-table)
|
||||||
|
|
||||||
table.bootstrapTable({
|
table.bootstrapTable({
|
||||||
@ -172,6 +175,7 @@ function loadBomTable(table, options) {
|
|||||||
queryParams: function(p) {
|
queryParams: function(p) {
|
||||||
return {
|
return {
|
||||||
part: options.parent_id,
|
part: options.parent_id,
|
||||||
|
ordering: 'name',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columns: cols,
|
columns: cols,
|
||||||
|
@ -119,13 +119,12 @@ function loadPartTable(table, url, options={}) {
|
|||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'full_name',
|
||||||
title: 'Part',
|
title: 'Part',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
formatter: function(value, row, index, field) {
|
formatter: function(value, row, index, field) {
|
||||||
var name = row.full_name;
|
|
||||||
|
|
||||||
var display = imageHoverIcon(row.image_url) + renderLink(name, row.url);
|
var display = imageHoverIcon(row.image_url) + renderLink(value, row.url);
|
||||||
if (!row.active) {
|
if (!row.active) {
|
||||||
display = display + "<span class='label label-warning' style='float: right;'>INACTIVE</span>";
|
display = display + "<span class='label label-warning' style='float: right;'>INACTIVE</span>";
|
||||||
}
|
}
|
||||||
@ -160,7 +159,7 @@ function loadPartTable(table, url, options={}) {
|
|||||||
return renderLink(value, row.url + 'stock/');
|
return renderLink(value, row.url + 'stock/');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return "<span class='label label-warning'>No stock</span>";
|
return "<span class='label label-warning'>No Stock</span>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
InvenTree/templates/InvenTree/password.html
Normal file
7
InvenTree/templates/InvenTree/password.html
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "modal_form.html" %}
|
||||||
|
|
||||||
|
{% block pre_form_content %}
|
||||||
|
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
{% endblock %}
|
66
InvenTree/templates/InvenTree/settings.html
Normal file
66
InvenTree/templates/InvenTree/settings.html
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block page_title %}
|
||||||
|
InvenTree | Settings
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h3>InvenTree Settings</h3>
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class='row'>
|
||||||
|
<div class='col-sm-6'>
|
||||||
|
<h4>User Information</h4>
|
||||||
|
</div>
|
||||||
|
<div class='col-sm-6'>
|
||||||
|
<div class='btn-group' style='float: right;'>
|
||||||
|
<div class='btn btn-primary' type='button' id='edit-user' title='Edit User Information'>Edit</div>
|
||||||
|
<div class='btn btn-primary' type='button' id='edit-password' title='Change Password'>Set Password</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<table class='table table-striped table-condensed'>
|
||||||
|
<tr>
|
||||||
|
<td>First Name</td>
|
||||||
|
<td>{{ user.first_name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Name</td>
|
||||||
|
<td>{{ user.last_name }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Email Address</td>
|
||||||
|
<td>{{ user.email }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_load %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js_ready %}
|
||||||
|
{{ block.super }}
|
||||||
|
|
||||||
|
$("#edit-user").on('click', function() {
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'edit-user' %}",
|
||||||
|
{
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#edit-password").on('click', function() {
|
||||||
|
launchModalForm(
|
||||||
|
"{% url 'set-password' %}",
|
||||||
|
{
|
||||||
|
reload: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -19,7 +19,9 @@
|
|||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<li><a href="/admin/"><span class="glyphicon glyphicon-edit"></span> Admin</a></li>
|
<li><a href="/admin/"><span class="glyphicon glyphicon-edit"></span> Admin</a></li>
|
||||||
|
<hr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<li><a href="{% url 'settings' %}"><span class="glyphicon glyphicon-cog"></span> Settings</a></li>
|
||||||
<li><a href="{% url 'logout' %}"><span class="glyphicon glyphicon-log-out"></span> Logout</a></li>
|
<li><a href="{% url 'logout' %}"><span class="glyphicon glyphicon-log-out"></span> Logout</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url 'login' %}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
|
<li><a href="{% url 'login' %}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
|
||||||
|
Loading…
Reference in New Issue
Block a user