2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-20 05:46:34 +00:00

Merge pull request #1716 from SchrodingersGat/drf-api-forms

[WIP] API Forms
This commit is contained in:
Oliver
2021-07-03 22:14:26 +10:00
committed by GitHub
225 changed files with 5557 additions and 3121 deletions

View File

@ -127,7 +127,10 @@ class CategoryList(generics.ListCreateAPIView):
class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
""" API endpoint for detail view of a single PartCategory object """
"""
API endpoint for detail view of a single PartCategory object
"""
serializer_class = part_serializers.CategorySerializer
queryset = PartCategory.objects.all()
@ -229,6 +232,24 @@ class PartAttachmentList(generics.ListCreateAPIView, AttachmentMixin):
]
class PartAttachmentDetail(generics.RetrieveUpdateDestroyAPIView, AttachmentMixin):
"""
Detail endpoint for PartAttachment model
"""
queryset = PartAttachment.objects.all()
serializer_class = part_serializers.PartAttachmentSerializer
class PartTestTemplateDetail(generics.RetrieveUpdateDestroyAPIView):
"""
Detail endpoint for PartTestTemplate model
"""
queryset = PartTestTemplate.objects.all()
serializer_class = part_serializers.PartTestTemplateSerializer
class PartTestTemplateList(generics.ListCreateAPIView):
"""
API endpoint for listing (and creating) a PartTestTemplate.
@ -337,8 +358,9 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView):
def get_serializer(self, *args, **kwargs):
# By default, include 'category_detail' information in the detail view
try:
kwargs['category_detail'] = str2bool(self.request.query_params.get('category_detail', False))
kwargs['category_detail'] = str2bool(self.request.query_params.get('category_detail', True))
except AttributeError:
pass
@ -1032,11 +1054,13 @@ part_api_urls = [
# Base URL for PartTestTemplate API endpoints
url(r'^test-template/', include([
url(r'^(?P<pk>\d+)/', PartTestTemplateDetail.as_view(), name='api-part-test-template-detail'),
url(r'^$', PartTestTemplateList.as_view(), name='api-part-test-template-list'),
])),
# Base URL for PartAttachment API endpoints
url(r'^attachment/', include([
url(r'^(?P<pk>\d+)/', PartAttachmentDetail.as_view(), name='api-part-attachment-detail'),
url(r'^$', PartAttachmentList.as_view(), name='api-part-attachment-list'),
])),

View File

@ -5,21 +5,21 @@ Django Forms for interacting with Part objects
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
from mptt.fields import TreeNodeChoiceField
from InvenTree.forms import HelperForm
from InvenTree.helpers import GetExportFormats
from InvenTree.fields import RoundingDecimalFormField
from mptt.fields import TreeNodeChoiceField
from django import forms
from django.utils.translation import ugettext_lazy as _
import common.models
from .models import Part, PartCategory, PartAttachment, PartRelated
from .models import Part, PartCategory, PartRelated
from .models import BomItem
from .models import PartParameterTemplate, PartParameter
from .models import PartCategoryParameterTemplate
from .models import PartTestTemplate
from .models import PartSellPriceBreak, PartInternalPriceBreak
@ -65,22 +65,6 @@ class PartImageForm(HelperForm):
]
class EditPartTestTemplateForm(HelperForm):
""" Class for creating / editing a PartTestTemplate object """
class Meta:
model = PartTestTemplate
fields = [
'part',
'test_name',
'description',
'required',
'requires_value',
'requires_attachment',
]
class BomExportForm(forms.Form):
""" Simple form to let user set BOM export options,
before exporting a BOM (bill of materials) file.
@ -185,18 +169,6 @@ class CreatePartRelatedForm(HelperForm):
}
class EditPartAttachmentForm(HelperForm):
""" Form for editing a PartAttachment object """
class Meta:
model = PartAttachment
fields = [
'part',
'attachment',
'comment'
]
class SetPartCategoryForm(forms.Form):
""" Form for setting the category of multiple Part objects """

View File

@ -75,6 +75,10 @@ class PartCategory(InvenTreeTree):
default_keywords = models.CharField(null=True, blank=True, max_length=250, verbose_name=_('Default keywords'), help_text=_('Default keywords for parts in this category'))
@staticmethod
def get_api_url():
return reverse('api-part-category-list')
def get_absolute_url(self):
return reverse('category-detail', kwargs={'pk': self.id})
@ -329,6 +333,11 @@ class Part(MPTTModel):
# For legacy reasons the 'variant_of' field is used to indicate the MPTT parent
parent_attr = 'variant_of'
@staticmethod
def get_api_url():
return reverse('api-part-list')
def get_context_data(self, request, **kwargs):
"""
Return some useful context data about this part for template rendering
@ -1968,6 +1977,10 @@ class PartAttachment(InvenTreeAttachment):
Model for storing file attachments against a Part object
"""
@staticmethod
def get_api_url():
return reverse('api-part-attachment-list')
def getSubdir(self):
return os.path.join("part_files", str(self.part.id))
@ -1979,6 +1992,10 @@ class PartSellPriceBreak(common.models.PriceBreak):
"""
Represents a price break for selling this part
"""
@staticmethod
def get_api_url():
return reverse('api-part-sale-price-list')
part = models.ForeignKey(
Part, on_delete=models.CASCADE,
@ -1996,6 +2013,10 @@ class PartInternalPriceBreak(common.models.PriceBreak):
Represents a price break for internally selling this part
"""
@staticmethod
def get_api_url():
return reverse('api-part-internal-price-list')
part = models.ForeignKey(
Part, on_delete=models.CASCADE,
related_name='internalpricebreaks',
@ -2040,6 +2061,10 @@ class PartTestTemplate(models.Model):
run on the model (refer to the validate_unique function).
"""
@staticmethod
def get_api_url():
return reverse('api-part-test-template-list')
def save(self, *args, **kwargs):
self.clean()
@ -2138,6 +2163,10 @@ class PartParameterTemplate(models.Model):
units: The units of the Parameter [string]
"""
@staticmethod
def get_api_url():
return reverse('api-part-param-template-list')
def __str__(self):
s = str(self.name)
if self.units:
@ -2175,6 +2204,10 @@ class PartParameter(models.Model):
data: The data (value) of the Parameter [string]
"""
@staticmethod
def get_api_url():
return reverse('api-part-param-list')
def __str__(self):
# String representation of a PartParameter (used in the admin interface)
return "{part} : {param} = {data}{units}".format(
@ -2266,6 +2299,10 @@ class BomItem(models.Model):
allow_variants: Stock for part variants can be substituted for this BomItem
"""
@staticmethod
def get_api_url():
return reverse('api-bom-list')
def save(self, *args, **kwargs):
self.clean()

View File

@ -4,6 +4,7 @@ JSON serializers for Part app
import imghdr
from decimal import Decimal
from django.urls import reverse_lazy
from django.db import models
from django.db.models import Q
from django.db.models.functions import Coalesce
@ -38,6 +39,7 @@ class CategorySerializer(InvenTreeModelSerializer):
'name',
'description',
'default_location',
'default_keywords',
'pathstring',
'url',
'parent',
@ -59,7 +61,12 @@ class PartAttachmentSerializer(InvenTreeModelSerializer):
'pk',
'part',
'attachment',
'comment'
'comment',
'upload_date',
]
read_only_fields = [
'upload_date',
]
@ -187,6 +194,9 @@ class PartSerializer(InvenTreeModelSerializer):
Used when displaying all details of a single component.
"""
def get_api_url(self):
return reverse_lazy('api-part-list')
def __init__(self, *args, **kwargs):
"""
Custom initialization method for PartSerializer,
@ -326,9 +336,10 @@ class PartSerializer(InvenTreeModelSerializer):
'category',
'category_detail',
'component',
'description',
'default_location',
'default_expiry',
'default_location',
'default_supplier',
'description',
'full_name',
'image',
'in_stock',

View File

@ -19,51 +19,68 @@
{% block js_ready %}
{{ block.super }}
loadAttachmentTable(
'{% url "api-part-attachment-list" %}',
{
filters: {
part: {{ part.pk }},
},
onEdit: function(pk) {
var url = `/api/part/attachment/${pk}/`;
constructForm(url, {
fields: {
comment: {},
},
title: '{% trans "Edit Attachment" %}',
onSuccess: reloadAttachmentTable,
});
},
onDelete: function(pk) {
var url = `/api/part/attachment/${pk}/`;
constructForm(url, {
method: 'DELETE',
confirmMessage: '{% trans "Confirm Delete Operation" %}',
title: '{% trans "Delete Attachment" %}',
onSuccess: reloadAttachmentTable,
});
}
}
);
enableDragAndDrop(
'#attachment-dropzone',
"{% url 'part-attachment-create' %}",
'{% url "api-part-attachment-list" %}',
{
data: {
part: {{ part.id }},
},
label: 'attachment',
success: function(data, status, xhr) {
location.reload();
reloadAttachmentTable();
}
}
);
$("#new-attachment").click(function() {
launchModalForm("{% url 'part-attachment-create' %}?part={{ part.id }}",
constructForm(
'{% url "api-part-attachment-list" %}',
{
reload: true,
});
});
$("#attachment-table").on('click', '.attachment-edit-button', function() {
var button = $(this);
var url = `/part/attachment/${button.attr('pk')}/edit/`;
launchModalForm(url,
{
reload: true,
});
});
$("#attachment-table").on('click', '.attachment-delete-button', function() {
var button = $(this);
var url = `/part/attachment/${button.attr('pk')}/delete/`;
launchModalForm(url, {
success: function() {
location.reload();
method: 'POST',
fields: {
attachment: {},
comment: {},
part: {
value: {{ part.pk }},
hidden: true,
}
},
onSuccess: reloadAttachmentTable,
title: '{% trans "Add Attachment" %}',
}
});
});
$("#attachment-table").inventreeTable({
)
});
{% endblock %}

View File

@ -1,16 +0,0 @@
{% extends "modal_delete_form.html" %}
{% load i18n %}
{% block pre_form_content %}
{% trans "Are you sure you want to delete this BOM item?" %}
<br>
{% trans "Deleting this entry will remove the BOM row from the following part" %}:
<ul class='list-group'>
<li class='list-group-item'>
<b>{{ item.part.full_name }}</b> - <i>{{ item.part.description }}</i>
</li>
</ul>
{% endblock %}

View File

@ -124,7 +124,7 @@
// Wait for *all* the requests to complete
$.when.apply($, requests).then(function() {
$('#bom-table').bootstrapTable('refresh');
location.reload();
});
}
}

View File

@ -268,13 +268,25 @@
{% if category %}
$("#cat-edit").click(function () {
launchModalForm(
"{% url 'category-edit' category.id %}",
constructForm(
'{% url "api-part-category-detail" category.pk %}',
{
fields: {
name: {},
description: {},
parent: {
help_text: '{% trans "Select parent category" %}',
},
default_location: {},
default_keywords: {
icon: 'fa-key',
}
},
title: '{% trans "Edit Part Category" %}',
reload: true
},
}
);
return false;
});
{% if category.parent %}

View File

@ -51,7 +51,6 @@
field: 'manufacturer',
label: '{% trans "New Manufacturer" %}',
title: '{% trans "Create new manufacturer" %}',
url: "{% url 'manufacturer-create' %}",
}
]
});
@ -61,17 +60,10 @@
var selections = $("#manufacturer-table").bootstrapTable("getSelections");
var parts = [];
selections.forEach(function(item) {
parts.push(item.pk);
});
launchModalForm("{% url 'manufacturer-part-delete' %}", {
data: {
parts: parts,
},
reload: true,
deleteManufacturerParts(selections, {
onSuccess: function() {
$("#manufacturer-table").bootstrapTable("refresh");
}
});
});
@ -81,7 +73,7 @@
{
params: {
part: {{ part.id }},
part_detail: false,
part_detail: true,
manufacturer_detail: true,
},
}

View File

@ -237,6 +237,16 @@
});
{% endif %}
function reloadImage(data) {
// If image / thumbnail data present, live update
if (data.image) {
$('#part-image').attr('src', data.image);
} else {
// Otherwise, reload the page
location.reload();
}
}
enableDragAndDrop(
'#part-thumb',
"{% url 'api-part-detail' part.id %}",
@ -244,14 +254,7 @@
label: 'image',
method: 'PATCH',
success: function(data, status, xhr) {
// If image / thumbnail data present, live update
if (data.image) {
$('#part-image').attr('src', data.image);
} else {
// Otherwise, reload the page
location.reload();
}
reloadImage(data);
}
}
);
@ -293,11 +296,20 @@
});
$("#part-image-upload").click(function() {
launchModalForm("{% url 'part-image-upload' part.id %}",
constructForm(
'{% url "api-part-detail" part.pk %}',
{
reload: true
method: 'PATCH',
fields: {
image: {},
},
title: '{% trans "Upload Image" %}',
onSuccess: function(data) {
reloadImage(data);
}
}
);
)
});
@ -357,6 +369,75 @@
});
$("#part-edit").click(function() {
constructForm('{% url "api-part-detail" part.id %}', {
focus: 'name',
fields: {
category: {
secondary: {
label: '{% trans "New Category" %}',
title: '{% trans "Create New Part Category" %}',
api_url: '{% url "api-part-category-list" %}',
method: 'POST',
fields: {
name: {},
description: {},
parent: {
secondary: {
title: '{% trans "New Parent" %}',
api_url: '{% url "api-part-category-list" %}',
method: 'POST',
fields: {
name: {},
description: {},
parent: {},
}
}
},
}
},
},
name: {
placeholder: 'part name',
},
IPN: {},
description: {},
revision: {},
keywords: {
icon: 'fa-key',
},
variant_of: {},
link: {
icon: 'fa-link',
},
default_location: {
secondary: {
label: '{% trans "New Location" %}',
title: '{% trans "Create new stock location" %}',
},
},
default_supplier: {
filters: {
part: {{ part.pk }},
part_detail: true,
manufacturer_detail: true,
supplier_detail: true,
},
secondary: {
label: '{% trans "New Supplier Part" %}',
title: '{% trans "Create new supplier part" %}',
}
},
units: {},
minimum_stock: {},
},
title: '{% trans "Edit Part" %}',
reload: true,
});
return;
launchModalForm(
"{% url 'part-edit' part.id %}",
{

View File

@ -44,34 +44,52 @@ function reloadTable() {
}
$("#add-test-template").click(function() {
launchModalForm(
"{% url 'part-test-template-create' %}",
{
data: {
part: {{ part.id }},
},
success: reloadTable,
}
);
constructForm('{% url "api-part-test-template-list" %}', {
method: 'POST',
fields: {
test_name: {},
description: {},
required: {},
requires_value: {},
requires_attachment: {},
part: {
value: {{ part.pk }},
hidden: true,
}
},
title: '{% trans "Add Test Result Template" %}',
onSuccess: reloadTable
});
});
$("#test-template-table").on('click', '.button-test-edit', function() {
var button = $(this);
var pk = $(this).attr('pk');
var url = `/part/test-template/${button.attr('pk')}/edit/`;
var url = `/api/part/test-template/${pk}/`;
launchModalForm(url, {
success: reloadTable,
constructForm(url, {
fields: {
test_name: {},
description: {},
required: {},
requires_value: {},
requires_attachment: {},
},
title: '{% trans "Edit Test Result Template" %}',
onSuccess: reloadTable,
});
});
$("#test-template-table").on('click', '.button-test-delete', function() {
var button = $(this);
var pk = $(this).attr('pk');
var url = `/part/test-template/${button.attr('pk')}/delete/`;
var url = `/api/part/test-template/${pk}/`;
launchModalForm(url, {
success: reloadTable,
constructForm(url, {
method: 'DELETE',
title: '{% trans "Delete Test Result Template" %}',
onSuccess: reloadTable,
});
});

View File

@ -49,13 +49,11 @@
field: 'supplier',
label: '{% trans "New Supplier" %}',
title: '{% trans "Create new supplier" %}',
url: "{% url 'supplier-create' %}"
},
{
field: 'manufacturer',
label: '{% trans "New Manufacturer" %}',
title: '{% trans "Create new manufacturer" %}',
url: "{% url 'manufacturer-create' %}",
}
]
});

View File

@ -16,6 +16,106 @@ from company.models import Company
from common.models import InvenTreeSetting
class PartOptionsAPITest(InvenTreeAPITestCase):
"""
Tests for the various OPTIONS endpoints in the /part/ API
Ensure that the required field details are provided!
"""
roles = [
'part.add',
]
def setUp(self):
super().setUp()
def test_part(self):
"""
Test the Part API OPTIONS
"""
actions = self.getActions(reverse('api-part-list'))['POST']
# Check that a bunch o' fields are contained
for f in ['assembly', 'component', 'description', 'image', 'IPN']:
self.assertTrue(f in actions.keys())
# Active is a 'boolean' field
active = actions['active']
self.assertTrue(active['default'])
self.assertEqual(active['help_text'], 'Is this part active?')
self.assertEqual(active['type'], 'boolean')
self.assertEqual(active['read_only'], False)
# String field
ipn = actions['IPN']
self.assertEqual(ipn['type'], 'string')
self.assertFalse(ipn['required'])
self.assertEqual(ipn['max_length'], 100)
self.assertEqual(ipn['help_text'], 'Internal Part Number')
# Related field
category = actions['category']
self.assertEqual(category['type'], 'related field')
self.assertTrue(category['required'])
self.assertFalse(category['read_only'])
self.assertEqual(category['label'], 'Category')
self.assertEqual(category['model'], 'partcategory')
self.assertEqual(category['api_url'], reverse('api-part-category-list'))
self.assertEqual(category['help_text'], 'Part category')
def test_category(self):
"""
Test the PartCategory API OPTIONS endpoint
"""
actions = self.getActions(reverse('api-part-category-list'))
# actions should *not* contain 'POST' as we do not have the correct role
self.assertFalse('POST' in actions)
self.assignRole('part_category.add')
actions = self.getActions(reverse('api-part-category-list'))['POST']
name = actions['name']
self.assertTrue(name['required'])
self.assertEqual(name['label'], 'Name')
loc = actions['default_location']
self.assertEqual(loc['api_url'], reverse('api-location-list'))
def test_bom_item(self):
"""
Test the BomItem API OPTIONS endpoint
"""
actions = self.getActions(reverse('api-bom-list'))['POST']
inherited = actions['inherited']
self.assertEqual(inherited['type'], 'boolean')
# 'part' reference
part = actions['part']
self.assertTrue(part['required'])
self.assertFalse(part['read_only'])
self.assertTrue(part['filters']['assembly'])
# 'sub_part' reference
sub_part = actions['sub_part']
self.assertTrue(sub_part['required'])
self.assertEqual(sub_part['type'], 'related field')
self.assertTrue(sub_part['filters']['component'])
class PartAPITest(InvenTreeAPITestCase):
"""
Series of tests for the Part DRF API

View File

@ -232,29 +232,6 @@ class PartRelatedTests(PartViewTestCase):
self.assertEqual(n, 1)
class PartAttachmentTests(PartViewTestCase):
def test_valid_create(self):
""" test creation of an attachment for a valid part """
response = self.client.get(reverse('part-attachment-create'), {'part': 1}, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
# TODO - Create a new attachment using this view
def test_invalid_create(self):
""" test creation of an attachment for an invalid part """
# TODO
pass
def test_edit(self):
""" test editing an attachment """
# TODO
pass
class PartQRTest(PartViewTestCase):
""" Tests for the Part QR Code AJAX view """
@ -294,11 +271,6 @@ class CategoryTest(PartViewTestCase):
# Form should still return OK
self.assertEqual(response.status_code, 200)
def test_edit(self):
""" Retrieve the part category editing form """
response = self.client.get(reverse('category-edit', args=(1,)), HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 200)
def test_set_category(self):
""" Test that the "SetCategory" view works """

View File

@ -17,12 +17,6 @@ part_related_urls = [
url(r'^(?P<pk>\d+)/delete/?', views.PartRelatedDelete.as_view(), name='part-related-delete'),
]
part_attachment_urls = [
url(r'^new/?', views.PartAttachmentCreate.as_view(), name='part-attachment-create'),
url(r'^(?P<pk>\d+)/edit/?', views.PartAttachmentEdit.as_view(), name='part-attachment-edit'),
url(r'^(?P<pk>\d+)/delete/?', views.PartAttachmentDelete.as_view(), name='part-attachment-delete'),
]
sale_price_break_urls = [
url(r'^new/', views.PartSalePriceBreakCreate.as_view(), name='sale-price-break-create'),
url(r'^(?P<pk>\d+)/edit/', views.PartSalePriceBreakEdit.as_view(), name='sale-price-break-edit'),
@ -79,7 +73,6 @@ part_detail_urls = [
url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
# Normal thumbnail with form
url(r'^thumbnail/?', views.PartImageUpload.as_view(), name='part-image-upload'),
url(r'^thumb-select/?', views.PartImageSelect.as_view(), name='part-image-select'),
url(r'^thumb-download/', views.PartImageDownloadFromURL.as_view(), name='part-image-download'),
@ -103,7 +96,6 @@ category_urls = [
# Category detail views
url(r'(?P<pk>\d+)/', include([
url(r'^edit/', views.CategoryEdit.as_view(), name='category-edit'),
url(r'^delete/', views.CategoryDelete.as_view(), name='category-delete'),
url(r'^parameters/', include(category_parameter_urls)),
@ -117,7 +109,6 @@ category_urls = [
part_bom_urls = [
url(r'^edit/?', views.BomItemEdit.as_view(), name='bom-item-edit'),
url('^delete/?', views.BomItemDelete.as_view(), name='bom-item-delete'),
]
# URL list for part web interface
@ -148,22 +139,12 @@ part_urls = [
# Part related
url(r'^related-parts/', include(part_related_urls)),
# Part attachments
url(r'^attachment/', include(part_attachment_urls)),
# Part price breaks
url(r'^sale-price/', include(sale_price_break_urls)),
# Part internal price breaks
url(r'^internal-price/', include(internal_price_break_urls)),
# Part test templates
url(r'^test-template/', include([
url(r'^new/', views.PartTestTemplateCreate.as_view(), name='part-test-template-create'),
url(r'^(?P<pk>\d+)/edit/', views.PartTestTemplateEdit.as_view(), name='part-test-template-edit'),
url(r'^(?P<pk>\d+)/delete/', views.PartTestTemplateDelete.as_view(), name='part-test-template-delete'),
])),
# Part parameters
url(r'^parameter/', include(part_parameter_urls)),

View File

@ -31,12 +31,11 @@ import io
from rapidfuzz import fuzz
from decimal import Decimal, InvalidOperation
from .models import PartCategory, Part, PartAttachment, PartRelated
from .models import PartCategory, Part, PartRelated
from .models import PartParameterTemplate, PartParameter
from .models import PartCategoryParameterTemplate
from .models import BomItem
from .models import match_part_names
from .models import PartTestTemplate
from .models import PartSellPriceBreak, PartInternalPriceBreak
from common.models import InvenTreeSetting
@ -155,146 +154,6 @@ class PartRelatedDelete(AjaxDeleteView):
role_required = 'part.change'
class PartAttachmentCreate(AjaxCreateView):
""" View for creating a new PartAttachment object
- The view only makes sense if a Part object is passed to it
"""
model = PartAttachment
form_class = part_forms.EditPartAttachmentForm
ajax_form_title = _("Add part attachment")
ajax_template_name = "modal_form.html"
def save(self, form, **kwargs):
"""
Record the user that uploaded this attachment
"""
attachment = form.save(commit=False)
attachment.user = self.request.user
attachment.save()
def get_data(self):
return {
'success': _('Added attachment')
}
def get_initial(self):
""" Get initial data for new PartAttachment object.
- Client should have requested this form with a parent part in mind
- e.g. ?part=<pk>
"""
initials = super(AjaxCreateView, self).get_initial()
# TODO - If the proper part was not sent, return an error message
try:
initials['part'] = Part.objects.get(id=self.request.GET.get('part', None))
except (ValueError, Part.DoesNotExist):
pass
return initials
def get_form(self):
""" Create a form to upload a new PartAttachment
- Hide the 'part' field
"""
form = super(AjaxCreateView, self).get_form()
form.fields['part'].widget = HiddenInput()
return form
class PartAttachmentEdit(AjaxUpdateView):
""" View for editing a PartAttachment object """
model = PartAttachment
form_class = part_forms.EditPartAttachmentForm
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Edit attachment')
def get_data(self):
return {
'success': _('Part attachment updated')
}
def get_form(self):
form = super(AjaxUpdateView, self).get_form()
form.fields['part'].widget = HiddenInput()
return form
class PartAttachmentDelete(AjaxDeleteView):
""" View for deleting a PartAttachment """
model = PartAttachment
ajax_form_title = _("Delete Part Attachment")
ajax_template_name = "attachment_delete.html"
context_object_name = "attachment"
role_required = 'part.change'
def get_data(self):
return {
'danger': _('Deleted part attachment')
}
class PartTestTemplateCreate(AjaxCreateView):
""" View for creating a PartTestTemplate """
model = PartTestTemplate
form_class = part_forms.EditPartTestTemplateForm
ajax_form_title = _("Create Test Template")
def get_initial(self):
initials = super().get_initial()
try:
part_id = self.request.GET.get('part', None)
initials['part'] = Part.objects.get(pk=part_id)
except (ValueError, Part.DoesNotExist):
pass
return initials
def get_form(self):
form = super().get_form()
form.fields['part'].widget = HiddenInput()
return form
class PartTestTemplateEdit(AjaxUpdateView):
""" View for editing a PartTestTemplate """
model = PartTestTemplate
form_class = part_forms.EditPartTestTemplateForm
ajax_form_title = _("Edit Test Template")
def get_form(self):
form = super().get_form()
form.fields['part'].widget = HiddenInput()
return form
class PartTestTemplateDelete(AjaxDeleteView):
""" View for deleting a PartTestTemplate """
model = PartTestTemplate
ajax_form_title = _("Delete Test Template")
class PartSetCategory(AjaxUpdateView):
""" View for settings the part category for multiple parts at once """
@ -1219,21 +1078,6 @@ class PartImageDownloadFromURL(AjaxUpdateView):
)
class PartImageUpload(AjaxUpdateView):
""" View for uploading a new Part image """
model = Part
ajax_template_name = 'modal_form.html'
ajax_form_title = _('Upload Part Image')
form_class = part_forms.PartImageForm
def get_data(self):
return {
'success': _('Updated part image'),
}
class PartImageSelect(AjaxUpdateView):
""" View for selecting Part image from existing images. """
@ -2934,17 +2778,10 @@ class BomItemEdit(AjaxUpdateView):
return form
class BomItemDelete(AjaxDeleteView):
""" Delete view for removing BomItem """
model = BomItem
ajax_template_name = 'part/bom-delete.html'
context_object_name = 'item'
ajax_form_title = _('Confim BOM item deletion')
class PartSalePriceBreakCreate(AjaxCreateView):
""" View for creating a sale price break for a part """
"""
View for creating a sale price break for a part
"""
model = PartSellPriceBreak
form_class = part_forms.EditPartSalePriceBreakForm