From 3c6d5756c57976882041cab912b5ab475cfd2a6d Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 13:21:20 +1000 Subject: [PATCH 01/14] Create a simple user settings view --- InvenTree/InvenTree/urls.py | 4 +++- InvenTree/InvenTree/views.py | 7 +++++++ InvenTree/templates/InvenTree/settings.html | 21 +++++++++++++++++++++ InvenTree/templates/navbar.html | 2 ++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 InvenTree/templates/InvenTree/settings.html diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index fe8a624458..572225864f 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -30,7 +30,7 @@ from django.conf.urls.static import static from django.views.generic.base import RedirectView from rest_framework.documentation import include_docs_urls -from .views import IndexView, SearchView +from .views import IndexView, SearchView, SettingsView from users.urls import user_urls @@ -61,6 +61,8 @@ 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'^admin/', admin.site.urls, name='inventree-admin'), diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 08ca9e0eb5..aea938f961 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -414,3 +414,10 @@ class SearchView(TemplateView): context['query'] = query return super(TemplateView, self).render_to_response(context) + + +class SettingsView(TemplateView): + """ View for configuring User settings + """ + + template_name = "InvenTree/settings.html" diff --git a/InvenTree/templates/InvenTree/settings.html b/InvenTree/templates/InvenTree/settings.html new file mode 100644 index 0000000000..99aa03ccb1 --- /dev/null +++ b/InvenTree/templates/InvenTree/settings.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block page_title %} +InvenTree | Settings +{% endblock %} + +{% block content %} +

Settings

+
+ +Logged in as {{ user.username }} + +{% endblock %} + +{% block js_load %} +{{ block.super }} +{% endblock %} + +{% block js_ready %} +{{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/navbar.html b/InvenTree/templates/navbar.html index 53a7bb4894..5cb71d13ac 100644 --- a/InvenTree/templates/navbar.html +++ b/InvenTree/templates/navbar.html @@ -19,7 +19,9 @@ {% if user.is_authenticated %} {% if user.is_staff %}
  • Admin
  • +
    {% endif %} +
  • Settings
  • Logout
  • {% else %}
  • Login
  • From 0032ea3409e3f113b35ea1274a5b1d9ed899e430 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 13:33:47 +1000 Subject: [PATCH 02/14] Add a form to edit basic user settings - First / last name - Email address --- InvenTree/InvenTree/forms.py | 15 +++++++- InvenTree/InvenTree/urls.py | 4 ++- InvenTree/InvenTree/views.py | 14 +++++++- InvenTree/templates/InvenTree/settings.html | 40 +++++++++++++++++++-- 4 files changed, 68 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 7ec4401101..901ef0b26a 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from django import forms from crispy_forms.helper import FormHelper - +from django.contrib.auth.models import User class HelperForm(forms.ModelForm): """ Provides simple integration of crispy_forms extension. """ @@ -33,3 +33,16 @@ class DeleteForm(forms.Form): fields = [ 'confirm_delete' ] + + +class EditUserForm(HelperForm): + """ Form for editing user information + """ + + class Meta: + model = User + fields = [ + 'first_name', + 'last_name', + 'email' + ] \ No newline at end of file diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 572225864f..0e24fddd47 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -30,7 +30,7 @@ from django.conf.urls.static import static from django.views.generic.base import RedirectView from rest_framework.documentation import include_docs_urls -from .views import IndexView, SearchView, SettingsView +from .views import IndexView, SearchView, SettingsView, EditUserView from users.urls import user_urls @@ -64,6 +64,8 @@ urlpatterns = [ url(r'^settings/', SettingsView.as_view(), name='settings'), + url(r'^edit-user/', EditUserView.as_view(), name='edit-user'), + url(r'^admin/', admin.site.urls, name='inventree-admin'), url(r'^qr_code/', include(qr_code_urls, namespace='qr_code')), diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index aea938f961..5b889246f6 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -17,7 +17,7 @@ from django.views.generic.base import TemplateView from part.models import Part -from .forms import DeleteForm +from .forms import DeleteForm, EditUserForm from .helpers import str2bool from rest_framework import views @@ -371,6 +371,18 @@ class AjaxDeleteView(AjaxMixin, UpdateView): 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 IndexView(TemplateView): """ View for InvenTree index page """ diff --git a/InvenTree/templates/InvenTree/settings.html b/InvenTree/templates/InvenTree/settings.html index 99aa03ccb1..56a8b1f0c2 100644 --- a/InvenTree/templates/InvenTree/settings.html +++ b/InvenTree/templates/InvenTree/settings.html @@ -5,10 +5,36 @@ InvenTree | Settings {% endblock %} {% block content %} -

    Settings

    +

    InvenTree Settings


    -Logged in as {{ user.username }} +
    +
    +

    User Information

    +
    +
    +
    +
    Edit
    +
    Set Password
    +
    +
    +
    + + + + + + + + + + + + + + + +
    First Name{{ user.first_name }}
    Last Name{{ user.last_name }}
    Email Address{{ user.email }}
    {% endblock %} @@ -18,4 +44,14 @@ Logged in as {{ user.username }} {% block js_ready %} {{ block.super }} + + $("#edit-user").on('click', function() { + launchModalForm( + "{% url 'edit-user' %}", + { + reload: true, + } + ); + }); + {% endblock %} \ No newline at end of file From b996c03f618303845755fa6ce813493da5e633df Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 13:58:23 +1000 Subject: [PATCH 03/14] Add a 'set password' form --- InvenTree/InvenTree/forms.py | 28 +++++++++++++- InvenTree/InvenTree/urls.py | 3 +- InvenTree/InvenTree/views.py | 43 ++++++++++++++++++++- InvenTree/templates/InvenTree/password.html | 7 ++++ InvenTree/templates/InvenTree/settings.html | 9 +++++ 5 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 InvenTree/templates/InvenTree/password.html diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index 901ef0b26a..f528c5c110 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -45,4 +45,30 @@ class EditUserForm(HelperForm): 'first_name', 'last_name', 'email' - ] \ No newline at end of file + ] + + +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' + ] diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 0e24fddd47..2872096c08 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -30,7 +30,7 @@ from django.conf.urls.static import static from django.views.generic.base import RedirectView from rest_framework.documentation import include_docs_urls -from .views import IndexView, SearchView, SettingsView, EditUserView +from .views import IndexView, SearchView, SettingsView, EditUserView, SetPasswordView from users.urls import user_urls @@ -65,6 +65,7 @@ urlpatterns = [ 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'), diff --git a/InvenTree/InvenTree/views.py b/InvenTree/InvenTree/views.py index 5b889246f6..041603f7fb 100644 --- a/InvenTree/InvenTree/views.py +++ b/InvenTree/InvenTree/views.py @@ -17,7 +17,7 @@ from django.views.generic.base import TemplateView from part.models import Part -from .forms import DeleteForm, EditUserForm +from .forms import DeleteForm, EditUserForm, SetPasswordForm from .helpers import str2bool from rest_framework import views @@ -382,6 +382,47 @@ class EditUserView(AjaxUpdateView): 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): """ View for InvenTree index page """ diff --git a/InvenTree/templates/InvenTree/password.html b/InvenTree/templates/InvenTree/password.html new file mode 100644 index 0000000000..d109a09a7a --- /dev/null +++ b/InvenTree/templates/InvenTree/password.html @@ -0,0 +1,7 @@ +{% extends "modal_form.html" %} + +{% block pre_form_content %} + +{{ block.super }} + +{% endblock %} \ No newline at end of file diff --git a/InvenTree/templates/InvenTree/settings.html b/InvenTree/templates/InvenTree/settings.html index 56a8b1f0c2..2a2bbcc144 100644 --- a/InvenTree/templates/InvenTree/settings.html +++ b/InvenTree/templates/InvenTree/settings.html @@ -54,4 +54,13 @@ InvenTree | Settings ); }); + $("#edit-password").on('click', function() { + launchModalForm( + "{% url 'set-password' %}", + { + reload: true, + } + ); + }); + {% endblock %} \ No newline at end of file From ba1e24cce8c73ee0030c31bf60499431b663f461 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 13:59:10 +1000 Subject: [PATCH 04/14] PEP --- InvenTree/InvenTree/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/forms.py b/InvenTree/InvenTree/forms.py index f528c5c110..c186ff8589 100644 --- a/InvenTree/InvenTree/forms.py +++ b/InvenTree/InvenTree/forms.py @@ -9,6 +9,7 @@ from django import forms from crispy_forms.helper import FormHelper from django.contrib.auth.models import User + class HelperForm(forms.ModelForm): """ Provides simple integration of crispy_forms extension. """ @@ -49,7 +50,7 @@ class EditUserForm(HelperForm): class SetPasswordForm(HelperForm): - """ Form for setting user password + """ Form for setting user password """ enter_password = forms.CharField(max_length=100, From df227a375c1cf5fdd0ad23505799e7c6f7177b9c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 15:42:53 +1000 Subject: [PATCH 05/14] Allow some more chars in part names --- InvenTree/InvenTree/validators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/validators.py b/InvenTree/InvenTree/validators.py index 88da882e10..f3fd9ef306 100644 --- a/InvenTree/InvenTree/validators.py +++ b/InvenTree/InvenTree/validators.py @@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _ def validate_part_name(value): # Prevent some illegal characters in part names - for c in ['/', '\\', '|', '#', '$']: + for c in ['|', '#', '$']: if c in str(value): raise ValidationError( _('Invalid character in part name') From 74c8b6768da1dd22ba0f4ecdba82296407eb39d9 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 15:43:16 +1000 Subject: [PATCH 06/14] BOM table fixes - Reorder columns - Allow proper part name filtering --- InvenTree/static/script/inventree/bom.js | 49 ++++++++++++------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index eaa1d40d3d..7d7fe6ab28 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -89,11 +89,11 @@ function loadBomTable(table, options) { // Part column cols.push( { - field: 'sub_part_detail', + field: 'sub_part_detail.full_name', title: 'Part', sortable: true, 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); } } ); @@ -115,29 +115,8 @@ function loadBomTable(table, options) { sortable: true, } ); - - // Part notes - cols.push( - { - field: 'note', - title: 'Notes', - searchable: true, - sortable: true, - } - ); - if (options.editable) { - cols.push({ - formatter: function(value, row, index, field) { - var bEdit = ""; - var bDelt = ""; - - return "
    " + bEdit + bDelt + "
    "; - } - }); - } - - else { + if (!options.editable) { cols.push( { field: 'sub_part_detail.available_stock', @@ -161,6 +140,27 @@ function loadBomTable(table, options) { } ); } + + // Part notes + cols.push( + { + field: 'note', + title: 'Notes', + searchable: true, + sortable: true, + } + ); + + if (options.editable) { + cols.push({ + formatter: function(value, row, index, field) { + var bEdit = ""; + var bDelt = ""; + + return "
    " + bEdit + bDelt + "
    "; + } + }); + } // Configure the table (bootstrap-table) @@ -172,6 +172,7 @@ function loadBomTable(table, options) { queryParams: function(p) { return { part: options.parent_id, + ordering: 'name', } }, columns: cols, From 7447561f77931314fdf5320bd3b94406a1d0be06 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:12:06 +1000 Subject: [PATCH 07/14] Fix link for part stock in BOM table --- InvenTree/static/script/inventree/bom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index 7d7fe6ab28..fd7f2ebd9d 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -135,7 +135,7 @@ function loadBomTable(table, options) { text = "" + value + ""; } - return renderLink(text, row.sub_part.url + "stock/"); + return renderLink(text, row.sub_part_detail.url + "stock/"); } } ); From 02033c2157a3cc239325b8dc1f9b56062f03c795 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:23:20 +1000 Subject: [PATCH 08/14] Add 'keywords' field to Part - Shows up in search results --- InvenTree/part/api.py | 1 + InvenTree/part/forms.py | 3 ++- .../part/migrations/0023_part_keywords.py | 18 ++++++++++++++++++ InvenTree/part/models.py | 5 ++++- InvenTree/part/serializers.py | 15 ++++++++------- InvenTree/part/templates/part/detail.html | 14 ++++++++++---- 6 files changed, 43 insertions(+), 13 deletions(-) create mode 100644 InvenTree/part/migrations/0023_part_keywords.py diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 0a2bdbfef6..e671b49e4f 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -157,6 +157,7 @@ class PartList(generics.ListCreateAPIView): '$name', 'description', '$IPN', + 'keywords', ] diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 580ed737a4..5dce37ac3b 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -92,9 +92,10 @@ class EditPartForm(HelperForm): 'confirm_creation', 'category', 'name', + 'IPN', 'variant', 'description', - 'IPN', + 'keywords', 'URL', 'default_location', 'default_supplier', diff --git a/InvenTree/part/migrations/0023_part_keywords.py b/InvenTree/part/migrations/0023_part_keywords.py new file mode 100644 index 0000000000..4752d80740 --- /dev/null +++ b/InvenTree/part/migrations/0023_part_keywords.py @@ -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), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 5a609c0e59..db9e79224b 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -179,8 +179,9 @@ class Part(models.Model): Attributes: name: Brief name for this part 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 + description: Longer form description of the part + keywords: Optional keywords for improving part search results IPN: Internal part number (optional) URL: Link to an external page with more information about this part (e.g. internal Wiki) image: Image of this part @@ -250,6 +251,8 @@ class Part(models.Model): 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', null=True, blank=True, on_delete=models.DO_NOTHING, diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 957bfa5951..4d667eb356 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -62,15 +62,16 @@ class PartSerializer(serializers.ModelSerializer): fields = [ 'pk', 'url', # Link to the part detail page - 'full_name', - 'name', - 'variant', - 'image_url', - 'IPN', - 'URL', # Link to an external URL (optional) - 'description', 'category', 'category_name', + 'image_url', + 'full_name', + 'name', + 'IPN', + 'variant', + 'description', + 'keywords', + 'URL', 'total_stock', 'available_stock', 'units', diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index c0f823ce13..a7bad6cae4 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -35,16 +35,22 @@ Part name {{ part.full_name }} - - Description - {{ part.description }} - {% if part.IPN %} IPN {{ part.IPN }} {% endif %} + + Description + {{ part.description }} + + {% if part.keywords %} + + Keywords + {{ part.keywords }} + + {% endif %} {% if part.URL %} URL From 0842bd5833bb31e878aa4e5deb39237264f4c52b Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:30:24 +1000 Subject: [PATCH 09/14] Add 'default_keywords' field to a category --- InvenTree/part/forms.py | 3 ++- .../0024_partcategory_default_keywords.py | 18 ++++++++++++++++++ InvenTree/part/models.py | 8 ++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 InvenTree/part/migrations/0024_partcategory_default_keywords.py diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index 5dce37ac3b..88c6c11385 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -119,7 +119,8 @@ class EditCategoryForm(HelperForm): 'parent', 'name', 'description', - 'default_location' + 'default_location', + 'default_keywords', ] diff --git a/InvenTree/part/migrations/0024_partcategory_default_keywords.py b/InvenTree/part/migrations/0024_partcategory_default_keywords.py new file mode 100644 index 0000000000..317d982f7d --- /dev/null +++ b/InvenTree/part/migrations/0024_partcategory_default_keywords.py @@ -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), + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index db9e79224b..3aeb94700a 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -37,6 +37,12 @@ from company.models import Company class PartCategory(InvenTreeTree): """ 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( @@ -46,6 +52,8 @@ class PartCategory(InvenTreeTree): 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): return reverse('category-detail', kwargs={'pk': self.id}) From d1de6eb1f96b296e5b05cde5892259706a604f3c Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 17:32:29 +1000 Subject: [PATCH 10/14] Copy across default tags when creating a new part --- InvenTree/part/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 8650b85fb4..fef88197f7 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -339,7 +339,9 @@ class PartCreate(AjaxCreateView): if self.get_category_id(): 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: pass From e87f545d75fbbdbef977a12e0eb87bf2f7a21604 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:12:17 +1000 Subject: [PATCH 11/14] Render zero stock as 'No Stock' in BOM table --- InvenTree/static/script/inventree/bom.js | 3 +++ InvenTree/static/script/inventree/part.js | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/InvenTree/static/script/inventree/bom.js b/InvenTree/static/script/inventree/bom.js index fd7f2ebd9d..46bdc249ef 100644 --- a/InvenTree/static/script/inventree/bom.js +++ b/InvenTree/static/script/inventree/bom.js @@ -132,6 +132,9 @@ function loadBomTable(table, options) { } else { + if (!value) { + value = 'No Stock'; + } text = "" + value + ""; } diff --git a/InvenTree/static/script/inventree/part.js b/InvenTree/static/script/inventree/part.js index c66fe405b2..51d86f1c7a 100644 --- a/InvenTree/static/script/inventree/part.js +++ b/InvenTree/static/script/inventree/part.js @@ -119,13 +119,12 @@ function loadPartTable(table, url, options={}) { visible: false, }, { - field: 'name', + field: 'full_name', title: 'Part', sortable: true, 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) { display = display + "INACTIVE"; } @@ -160,7 +159,7 @@ function loadPartTable(table, url, options={}) { return renderLink(value, row.url + 'stock/'); } else { - return "No stock"; + return "No Stock"; } } } From 9986df2074996d00c4f1cb9339d6183089a93421 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:20:54 +1000 Subject: [PATCH 12/14] Add confirmation to cancel a build --- InvenTree/build/forms.py | 12 +++++++++ InvenTree/build/templates/build/cancel.html | 6 ++++- InvenTree/build/views.py | 29 ++++++++++++++------- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/InvenTree/build/forms.py b/InvenTree/build/forms.py index 66ec98ac77..01024230f4 100644 --- a/InvenTree/build/forms.py +++ b/InvenTree/build/forms.py @@ -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): """ Form for adding a new BuildItem to a Build """ diff --git a/InvenTree/build/templates/build/cancel.html b/InvenTree/build/templates/build/cancel.html index d273a14ff5..d7e4d51b10 100644 --- a/InvenTree/build/templates/build/cancel.html +++ b/InvenTree/build/templates/build/cancel.html @@ -1,3 +1,7 @@ +{% extends "modal_form.html" %} + +{% block pre_form_content %} + Are you sure you wish to cancel this build? -{% include "modal_csrf.html" %} \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 85d07858fb..e4149c171b 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -16,6 +16,7 @@ from . import forms from stock.models import StockLocation, StockItem from InvenTree.views import AjaxView, AjaxUpdateView, AjaxCreateView, AjaxDeleteView +from InvenTree.helpers import str2bool class BuildIndex(ListView): @@ -41,31 +42,41 @@ class BuildIndex(ListView): return context -class BuildCancel(AjaxView): +class BuildCancel(AjaxUpdateView): """ View to cancel a Build. Provides a cancellation information dialog """ + model = Build ajax_template_name = 'build/cancel.html' ajax_form_title = 'Cancel Build' context_object_name = 'build' - fields = [] + form_class = forms.CancelBuildForm def post(self, request, *args, **kwargs): """ Handle POST request. Mark the build status as CANCELLED """ - build = get_object_or_404(Build, pk=self.kwargs['pk']) + build = self.get_object() - build.cancelBuild(request.user) + form = self.get_form() - return self.renderJsonResponse(request, None) + valid = form.is_valid() - def get_data(self): - """ Provide JSON context data. """ - return { + confirm = str2bool(request.POST.get('confirm_cancel', False)) + + if confirm: + build.cancelBuild(request.user) + else: + form.errors['confirm_cancel'] = ['Confirm build cancellation'] + valid = False + + data = { + 'form_valid': valid, 'danger': 'Build was cancelled' } + return self.renderJsonResponse(request, form, data=data) + class BuildAutoAllocate(AjaxUpdateView): """ View to auto-allocate parts for a build. @@ -217,7 +228,7 @@ class BuildComplete(AjaxUpdateView): form = self.get_form() - confirm = request.POST.get('confirm', False) + confirm = str2bool(request.POST.get('confirm', False)) loc_id = request.POST.get('location', None) From 46ab6e40ebeaa117ed58e3dcbc0033d775c4db63 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:31:19 +1000 Subject: [PATCH 13/14] Bug fix for build allocation - If the part did not have an image file the template failed to render --- InvenTree/build/templates/build/auto_allocate.html | 4 ++-- InvenTree/build/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/InvenTree/build/templates/build/auto_allocate.html b/InvenTree/build/templates/build/auto_allocate.html index f850d094b2..dc2160a006 100644 --- a/InvenTree/build/templates/build/auto_allocate.html +++ b/InvenTree/build/templates/build/auto_allocate.html @@ -22,8 +22,8 @@ Automatically allocate stock to this build? - - + + diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index e4149c171b..93611bae45 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -101,7 +101,7 @@ class BuildAutoAllocate(AjaxUpdateView): context['build'] = build context['allocations'] = build.getAutoAllocations() except Build.DoesNotExist: - context['error'] = 'No matching buidl found' + context['error'] = 'No matching build found' return context From 2164cac28a5e0518daa6779d97a028bbe2619d9f Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 14 May 2019 18:32:20 +1000 Subject: [PATCH 14/14] PEP --- InvenTree/build/views.py | 4 +--- InvenTree/part/serializers.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/InvenTree/build/views.py b/InvenTree/build/views.py index 93611bae45..7cef486d55 100644 --- a/InvenTree/build/views.py +++ b/InvenTree/build/views.py @@ -5,8 +5,6 @@ Django views for interacting with Build objects # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.shortcuts import get_object_or_404 - from django.views.generic import DetailView, ListView from django.forms import HiddenInput @@ -15,7 +13,7 @@ from .models import Build, BuildItem from . import forms 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 diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 4d667eb356..37ccb639a0 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -71,7 +71,7 @@ class PartSerializer(serializers.ModelSerializer): 'variant', 'description', 'keywords', - 'URL', + 'URL', 'total_stock', 'available_stock', 'units',