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
bb4c25ba68
@ -308,7 +308,10 @@ STATICFILES_DIRS = [
|
||||
MEDIA_URL = '/media/'
|
||||
|
||||
# The filesystem location for served static files
|
||||
MEDIA_ROOT = CONFIG.get('media_root', os.path.join(BASE_DIR, 'media'))
|
||||
MEDIA_ROOT = os.path.abspath(CONFIG.get('media_root', os.path.join(BASE_DIR, 'media')))
|
||||
|
||||
if DEBUG:
|
||||
print("MEDIA_ROOT:", MEDIA_ROOT)
|
||||
|
||||
# crispy forms use the bootstrap templates
|
||||
CRISPY_TEMPLATE_PACK = 'bootstrap'
|
||||
|
@ -183,6 +183,24 @@
|
||||
-webkit-opacity: 10%;
|
||||
}
|
||||
|
||||
/* grid display for part images */
|
||||
|
||||
.table-img-grid tr {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.table-img-grid td {
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.table-img-grid .grid-image {
|
||||
|
||||
height: 128px;
|
||||
width: 128px;
|
||||
object-fit: contain;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.btn-glyph {
|
||||
padding-left: 6px;
|
||||
@ -211,6 +229,20 @@
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.part-thumb-container:hover .part-thumb-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.part-thumb-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: .25s ease;
|
||||
padding: 15px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
18
InvenTree/build/migrations/0009_auto_20200210_1032.py
Normal file
18
InvenTree/build/migrations/0009_auto_20200210_1032.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-10 10:32
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('build', '0008_auto_20200201_1247'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='build',
|
||||
name='creation_date',
|
||||
field=models.DateField(auto_now_add=True),
|
||||
),
|
||||
]
|
@ -82,7 +82,7 @@ class Build(models.Model):
|
||||
batch = models.CharField(max_length=100, blank=True, null=True,
|
||||
help_text=_('Batch code for this build output'))
|
||||
|
||||
creation_date = models.DateField(auto_now=True, editable=False)
|
||||
creation_date = models.DateField(auto_now_add=True, editable=False)
|
||||
|
||||
completion_date = models.DateField(null=True, blank=True)
|
||||
|
||||
|
@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-02-03 10:28+0000\n"
|
||||
"POT-Creation-Date: 2020-02-10 11:09+0000\n"
|
||||
"PO-Revision-Date: 2020-02-02 08:07+0100\n"
|
||||
"Last-Translator: Christian Schlüter <chschlue@gmail.com>\n"
|
||||
"Language-Team: C <kde-i18n-doc@kde.org>\n"
|
||||
@ -706,7 +706,7 @@ msgstr "Link auf externe Seite"
|
||||
msgid "Order notes"
|
||||
msgstr "Bestell-Notizen"
|
||||
|
||||
#: order/models.py:159 order/models.py:210 part/views.py:1067
|
||||
#: order/models.py:159 order/models.py:210 part/views.py:1080
|
||||
#: stock/models.py:440
|
||||
msgid "Quantity must be greater than zero"
|
||||
msgstr "Anzahl muss größer Null sein"
|
||||
@ -1277,56 +1277,208 @@ msgstr "Tracking"
|
||||
msgid "Attachments"
|
||||
msgstr "Anhänge"
|
||||
|
||||
#: part/views.py:77
|
||||
#, fuzzy
|
||||
#| msgid "Add Attachment"
|
||||
msgid "Added attachment"
|
||||
msgstr "Anhang hinzufügen"
|
||||
|
||||
#: part/views.py:119
|
||||
#, fuzzy
|
||||
#| msgid "Part Attachments"
|
||||
msgid "Part attachment updated"
|
||||
msgstr "Anhänge"
|
||||
|
||||
#: part/views.py:196
|
||||
#, python-brace-format
|
||||
msgid "Set category for {n} parts"
|
||||
msgstr "Kategorie für {n} Teile setzen"
|
||||
|
||||
#: part/views.py:808
|
||||
#: part/views.py:306
|
||||
#, fuzzy
|
||||
#| msgid "Supplier part"
|
||||
msgid "Copied part"
|
||||
msgstr "Zulieferer-Teil"
|
||||
|
||||
#: part/views.py:414
|
||||
#, fuzzy
|
||||
#| msgid "Create new Stock Item"
|
||||
msgid "Create new part"
|
||||
msgstr "Neues Lagerobjekt hinzufügen"
|
||||
|
||||
#: part/views.py:419
|
||||
#, fuzzy
|
||||
#| msgid "Created new stock item"
|
||||
msgid "Created new part"
|
||||
msgstr "Neues Lagerobjekt erstellt"
|
||||
|
||||
#: part/views.py:609
|
||||
msgid "Upload Part Image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:614
|
||||
msgid "Updated part image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:623
|
||||
#, fuzzy
|
||||
#| msgid "Select part"
|
||||
msgid "Select Part Image"
|
||||
msgstr "Teil auswählen"
|
||||
|
||||
#: part/views.py:627
|
||||
#, fuzzy
|
||||
#| msgid "Select part"
|
||||
msgid "Selected part image"
|
||||
msgstr "Teil auswählen"
|
||||
|
||||
#: part/views.py:637
|
||||
#, fuzzy
|
||||
#| msgid "Edit notes"
|
||||
msgid "Edit Part Properties"
|
||||
msgstr "Bermerkungen bearbeiten"
|
||||
|
||||
#: part/views.py:659
|
||||
msgid "Validate BOM"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:821
|
||||
msgid "No BOM file provided"
|
||||
msgstr "Keine Stückliste angegeben"
|
||||
|
||||
#: part/views.py:1069
|
||||
#: part/views.py:1082
|
||||
msgid "Enter a valid quantity"
|
||||
msgstr "Bitte eine gültige Anzahl eingeben"
|
||||
|
||||
#: part/views.py:1093 part/views.py:1096
|
||||
#: part/views.py:1106 part/views.py:1109
|
||||
msgid "Select valid part"
|
||||
msgstr "Bitte ein gültiges Teil auswählen"
|
||||
|
||||
#: part/views.py:1102
|
||||
#: part/views.py:1115
|
||||
msgid "Duplicate part selected"
|
||||
msgstr "Teil doppelt ausgewählt"
|
||||
|
||||
#: part/views.py:1130
|
||||
#: part/views.py:1143
|
||||
msgid "Select a part"
|
||||
msgstr "Teil auswählen"
|
||||
|
||||
#: part/views.py:1134
|
||||
#: part/views.py:1147
|
||||
msgid "Specify quantity"
|
||||
msgstr "Anzahl angeben"
|
||||
|
||||
#: stock/forms.py:92
|
||||
#: part/views.py:1324
|
||||
#, fuzzy
|
||||
#| msgid "Confirm part creation"
|
||||
msgid "Confirm Part Deletion"
|
||||
msgstr "Erstellen des Teils bestätigen"
|
||||
|
||||
#: part/views.py:1331
|
||||
msgid "Part was deleted"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1340
|
||||
#, fuzzy
|
||||
#| msgid "Part packaging"
|
||||
msgid "Part Pricing"
|
||||
msgstr "Teile-Packaging"
|
||||
|
||||
#: part/views.py:1462
|
||||
#, fuzzy
|
||||
#| msgid "Parameter Template"
|
||||
msgid "Create Part Parameter Template"
|
||||
msgstr "Parameter Vorlage"
|
||||
|
||||
#: part/views.py:1470
|
||||
#, fuzzy
|
||||
#| msgid "Parameter Template"
|
||||
msgid "Edit Part Parameter Template"
|
||||
msgstr "Parameter Vorlage"
|
||||
|
||||
#: part/views.py:1477
|
||||
#, fuzzy
|
||||
#| msgid "Parameter Template"
|
||||
msgid "Delete Part Parameter Template"
|
||||
msgstr "Parameter Vorlage"
|
||||
|
||||
#: part/views.py:1485
|
||||
msgid "Create Part Parameter"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1535
|
||||
#, fuzzy
|
||||
#| msgid "Edit attachment"
|
||||
msgid "Edit Part Parameter"
|
||||
msgstr "Anhang bearbeiten"
|
||||
|
||||
#: part/views.py:1549
|
||||
#, fuzzy
|
||||
#| msgid "Delete attachment"
|
||||
msgid "Delete Part Parameter"
|
||||
msgstr "Anhang löschen"
|
||||
|
||||
#: part/views.py:1565
|
||||
#, fuzzy
|
||||
#| msgid "Part category"
|
||||
msgid "Edit Part Category"
|
||||
msgstr "Teile-Kategorie"
|
||||
|
||||
#: part/views.py:1600
|
||||
#, fuzzy
|
||||
#| msgid "Select part category"
|
||||
msgid "Delete Part Category"
|
||||
msgstr "Teilekategorie wählen"
|
||||
|
||||
#: part/views.py:1606
|
||||
#, fuzzy
|
||||
#| msgid "Part category"
|
||||
msgid "Part category was deleted"
|
||||
msgstr "Teile-Kategorie"
|
||||
|
||||
#: part/views.py:1614
|
||||
#, fuzzy
|
||||
#| msgid "Select part category"
|
||||
msgid "Create new part category"
|
||||
msgstr "Teilekategorie wählen"
|
||||
|
||||
#: part/views.py:1665
|
||||
#, fuzzy
|
||||
#| msgid "Created new stock item"
|
||||
msgid "Create BOM item"
|
||||
msgstr "Neues Lagerobjekt erstellt"
|
||||
|
||||
#: part/views.py:1731
|
||||
#, fuzzy
|
||||
#| msgid "Edit Stock Item"
|
||||
msgid "Edit BOM item"
|
||||
msgstr "Lagerobjekt bearbeiten"
|
||||
|
||||
#: part/views.py:1779
|
||||
#, fuzzy
|
||||
#| msgid "Confirm build completion"
|
||||
msgid "Confim BOM item deletion"
|
||||
msgstr "Bau-Fertigstellung bestätigen"
|
||||
|
||||
#: stock/forms.py:91
|
||||
msgid "File Format"
|
||||
msgstr "Dateiformat"
|
||||
|
||||
#: stock/forms.py:92
|
||||
#: stock/forms.py:91
|
||||
msgid "Select output file format"
|
||||
msgstr "Ausgabe-Dateiformat auswählen"
|
||||
|
||||
#: stock/forms.py:94
|
||||
#: stock/forms.py:93
|
||||
msgid "Include stock items in sub locations"
|
||||
msgstr "Lagerobjekte in untergeordneten Lagerorten einschließen"
|
||||
|
||||
#: stock/forms.py:127
|
||||
#: stock/forms.py:126
|
||||
msgid "Destination stock location"
|
||||
msgstr "Ziel-Lagerbestand"
|
||||
|
||||
#: stock/forms.py:133
|
||||
#: stock/forms.py:132
|
||||
msgid "Confirm movement of stock items"
|
||||
msgstr "Bewegung der Lagerobjekte bestätigen"
|
||||
|
||||
#: stock/forms.py:135
|
||||
#: stock/forms.py:134
|
||||
msgid "Set the destination as the default location for selected parts"
|
||||
msgstr "Setze das Ziel als Standard-Ziel für ausgewählte Teile"
|
||||
|
||||
@ -1652,27 +1804,33 @@ msgstr "Ungültige Menge"
|
||||
msgid "Invalid part selection"
|
||||
msgstr "Ungültige Teileauswahl"
|
||||
|
||||
#: stock/views.py:925
|
||||
#: stock/views.py:910
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "Created new stock item"
|
||||
msgid "Created {n} new stock items"
|
||||
msgstr "Neues Lagerobjekt erstellt"
|
||||
|
||||
#: stock/views.py:927 stock/views.py:940
|
||||
msgid "Created new stock item"
|
||||
msgstr "Neues Lagerobjekt erstellt"
|
||||
|
||||
#: stock/views.py:942
|
||||
#: stock/views.py:957
|
||||
msgid "Delete Stock Location"
|
||||
msgstr "Standort löschen"
|
||||
|
||||
#: stock/views.py:955
|
||||
#: stock/views.py:970
|
||||
msgid "Delete Stock Item"
|
||||
msgstr "Lagerobjekt löschen"
|
||||
|
||||
#: stock/views.py:966
|
||||
#: stock/views.py:981
|
||||
msgid "Delete Stock Tracking Entry"
|
||||
msgstr "Lagerbestands-Tracking-Eintrag löschen"
|
||||
|
||||
#: stock/views.py:983
|
||||
#: stock/views.py:998
|
||||
msgid "Edit Stock Tracking Entry"
|
||||
msgstr "Lagerbestands-Tracking-Eintrag bearbeiten"
|
||||
|
||||
#: stock/views.py:992
|
||||
#: stock/views.py:1007
|
||||
msgid "Add Stock Tracking Entry"
|
||||
msgstr "Lagerbestands-Tracking-Eintrag hinzufügen"
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-02-03 10:28+0000\n"
|
||||
"POT-Creation-Date: 2020-02-10 11:09+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -675,7 +675,7 @@ msgstr ""
|
||||
msgid "Order notes"
|
||||
msgstr ""
|
||||
|
||||
#: order/models.py:159 order/models.py:210 part/views.py:1067
|
||||
#: order/models.py:159 order/models.py:210 part/views.py:1080
|
||||
#: stock/models.py:440
|
||||
msgid "Quantity must be greater than zero"
|
||||
msgstr ""
|
||||
@ -1242,56 +1242,164 @@ msgstr ""
|
||||
msgid "Attachments"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:77
|
||||
msgid "Added attachment"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:119
|
||||
msgid "Part attachment updated"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:196
|
||||
#, python-brace-format
|
||||
msgid "Set category for {n} parts"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:808
|
||||
#: part/views.py:306
|
||||
msgid "Copied part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:414
|
||||
msgid "Create new part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:419
|
||||
msgid "Created new part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:609
|
||||
msgid "Upload Part Image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:614
|
||||
msgid "Updated part image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:623
|
||||
msgid "Select Part Image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:627
|
||||
msgid "Selected part image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:637
|
||||
msgid "Edit Part Properties"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:659
|
||||
msgid "Validate BOM"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:821
|
||||
msgid "No BOM file provided"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1069
|
||||
#: part/views.py:1082
|
||||
msgid "Enter a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1093 part/views.py:1096
|
||||
#: part/views.py:1106 part/views.py:1109
|
||||
msgid "Select valid part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1102
|
||||
#: part/views.py:1115
|
||||
msgid "Duplicate part selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1130
|
||||
#: part/views.py:1143
|
||||
msgid "Select a part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1134
|
||||
#: part/views.py:1147
|
||||
msgid "Specify quantity"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:92
|
||||
#: part/views.py:1324
|
||||
msgid "Confirm Part Deletion"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1331
|
||||
msgid "Part was deleted"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1340
|
||||
msgid "Part Pricing"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1462
|
||||
msgid "Create Part Parameter Template"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1470
|
||||
msgid "Edit Part Parameter Template"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1477
|
||||
msgid "Delete Part Parameter Template"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1485
|
||||
msgid "Create Part Parameter"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1535
|
||||
msgid "Edit Part Parameter"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1549
|
||||
msgid "Delete Part Parameter"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1565
|
||||
msgid "Edit Part Category"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1600
|
||||
msgid "Delete Part Category"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1606
|
||||
msgid "Part category was deleted"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1614
|
||||
msgid "Create new part category"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1665
|
||||
msgid "Create BOM item"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1731
|
||||
msgid "Edit BOM item"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1779
|
||||
msgid "Confim BOM item deletion"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:91
|
||||
msgid "File Format"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:92
|
||||
#: stock/forms.py:91
|
||||
msgid "Select output file format"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:94
|
||||
#: stock/forms.py:93
|
||||
msgid "Include stock items in sub locations"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:127
|
||||
#: stock/forms.py:126
|
||||
msgid "Destination stock location"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:133
|
||||
#: stock/forms.py:132
|
||||
msgid "Confirm movement of stock items"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:135
|
||||
#: stock/forms.py:134
|
||||
msgid "Set the destination as the default location for selected parts"
|
||||
msgstr ""
|
||||
|
||||
@ -1610,27 +1718,32 @@ msgstr ""
|
||||
msgid "Invalid part selection"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:925
|
||||
#: stock/views.py:910
|
||||
#, python-brace-format
|
||||
msgid "Created {n} new stock items"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:927 stock/views.py:940
|
||||
msgid "Created new stock item"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:942
|
||||
#: stock/views.py:957
|
||||
msgid "Delete Stock Location"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:955
|
||||
#: stock/views.py:970
|
||||
msgid "Delete Stock Item"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:966
|
||||
#: stock/views.py:981
|
||||
msgid "Delete Stock Tracking Entry"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:983
|
||||
#: stock/views.py:998
|
||||
msgid "Edit Stock Tracking Entry"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:992
|
||||
#: stock/views.py:1007
|
||||
msgid "Add Stock Tracking Entry"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-02-03 10:28+0000\n"
|
||||
"POT-Creation-Date: 2020-02-10 11:09+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -675,7 +675,7 @@ msgstr ""
|
||||
msgid "Order notes"
|
||||
msgstr ""
|
||||
|
||||
#: order/models.py:159 order/models.py:210 part/views.py:1067
|
||||
#: order/models.py:159 order/models.py:210 part/views.py:1080
|
||||
#: stock/models.py:440
|
||||
msgid "Quantity must be greater than zero"
|
||||
msgstr ""
|
||||
@ -1242,56 +1242,164 @@ msgstr ""
|
||||
msgid "Attachments"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:77
|
||||
msgid "Added attachment"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:119
|
||||
msgid "Part attachment updated"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:196
|
||||
#, python-brace-format
|
||||
msgid "Set category for {n} parts"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:808
|
||||
#: part/views.py:306
|
||||
msgid "Copied part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:414
|
||||
msgid "Create new part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:419
|
||||
msgid "Created new part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:609
|
||||
msgid "Upload Part Image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:614
|
||||
msgid "Updated part image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:623
|
||||
msgid "Select Part Image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:627
|
||||
msgid "Selected part image"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:637
|
||||
msgid "Edit Part Properties"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:659
|
||||
msgid "Validate BOM"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:821
|
||||
msgid "No BOM file provided"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1069
|
||||
#: part/views.py:1082
|
||||
msgid "Enter a valid quantity"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1093 part/views.py:1096
|
||||
#: part/views.py:1106 part/views.py:1109
|
||||
msgid "Select valid part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1102
|
||||
#: part/views.py:1115
|
||||
msgid "Duplicate part selected"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1130
|
||||
#: part/views.py:1143
|
||||
msgid "Select a part"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1134
|
||||
#: part/views.py:1147
|
||||
msgid "Specify quantity"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:92
|
||||
#: part/views.py:1324
|
||||
msgid "Confirm Part Deletion"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1331
|
||||
msgid "Part was deleted"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1340
|
||||
msgid "Part Pricing"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1462
|
||||
msgid "Create Part Parameter Template"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1470
|
||||
msgid "Edit Part Parameter Template"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1477
|
||||
msgid "Delete Part Parameter Template"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1485
|
||||
msgid "Create Part Parameter"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1535
|
||||
msgid "Edit Part Parameter"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1549
|
||||
msgid "Delete Part Parameter"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1565
|
||||
msgid "Edit Part Category"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1600
|
||||
msgid "Delete Part Category"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1606
|
||||
msgid "Part category was deleted"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1614
|
||||
msgid "Create new part category"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1665
|
||||
msgid "Create BOM item"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1731
|
||||
msgid "Edit BOM item"
|
||||
msgstr ""
|
||||
|
||||
#: part/views.py:1779
|
||||
msgid "Confim BOM item deletion"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:91
|
||||
msgid "File Format"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:92
|
||||
#: stock/forms.py:91
|
||||
msgid "Select output file format"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:94
|
||||
#: stock/forms.py:93
|
||||
msgid "Include stock items in sub locations"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:127
|
||||
#: stock/forms.py:126
|
||||
msgid "Destination stock location"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:133
|
||||
#: stock/forms.py:132
|
||||
msgid "Confirm movement of stock items"
|
||||
msgstr ""
|
||||
|
||||
#: stock/forms.py:135
|
||||
#: stock/forms.py:134
|
||||
msgid "Set the destination as the default location for selected parts"
|
||||
msgstr ""
|
||||
|
||||
@ -1610,27 +1718,32 @@ msgstr ""
|
||||
msgid "Invalid part selection"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:925
|
||||
#: stock/views.py:910
|
||||
#, python-brace-format
|
||||
msgid "Created {n} new stock items"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:927 stock/views.py:940
|
||||
msgid "Created new stock item"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:942
|
||||
#: stock/views.py:957
|
||||
msgid "Delete Stock Location"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:955
|
||||
#: stock/views.py:970
|
||||
msgid "Delete Stock Item"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:966
|
||||
#: stock/views.py:981
|
||||
msgid "Delete Stock Tracking Entry"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:983
|
||||
#: stock/views.py:998
|
||||
msgid "Edit Stock Tracking Entry"
|
||||
msgstr ""
|
||||
|
||||
#: stock/views.py:992
|
||||
#: stock/views.py:1007
|
||||
msgid "Add Stock Tracking Entry"
|
||||
msgstr ""
|
||||
|
||||
|
@ -8,7 +8,7 @@ from __future__ import unicode_literals
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from django.conf import settings
|
||||
|
||||
from django.db.models import Sum
|
||||
from django.db.models import Sum, Count
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
@ -23,10 +23,7 @@ import os
|
||||
from .models import Part, PartCategory, BomItem, PartStar
|
||||
from .models import PartParameter, PartParameterTemplate
|
||||
|
||||
from .serializers import PartSerializer, BomItemSerializer
|
||||
from .serializers import CategorySerializer
|
||||
from .serializers import PartStarSerializer
|
||||
from .serializers import PartParameterSerializer, PartParameterTemplateSerializer
|
||||
from . import serializers as part_serializers
|
||||
|
||||
from InvenTree.views import TreeSerializer
|
||||
from InvenTree.helpers import str2bool
|
||||
@ -53,7 +50,7 @@ class CategoryList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
queryset = PartCategory.objects.all()
|
||||
serializer_class = CategorySerializer
|
||||
serializer_class = part_serializers.CategorySerializer
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
@ -83,14 +80,37 @@ class CategoryList(generics.ListCreateAPIView):
|
||||
|
||||
class CategoryDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
""" API endpoint for detail view of a single PartCategory object """
|
||||
serializer_class = CategorySerializer
|
||||
serializer_class = part_serializers.CategorySerializer
|
||||
queryset = PartCategory.objects.all()
|
||||
|
||||
|
||||
class PartThumbs(generics.ListAPIView):
|
||||
""" API endpoint for retrieving information on available Part thumbnails """
|
||||
|
||||
serializer_class = part_serializers.PartThumbSerializer
|
||||
|
||||
def list(self, reguest, *args, **kwargs):
|
||||
"""
|
||||
Serialize the available Part images.
|
||||
- Images may be used for multiple parts!
|
||||
"""
|
||||
|
||||
# Get all Parts which have an associated image
|
||||
queryset = Part.objects.all().exclude(image='')
|
||||
|
||||
# Return the most popular parts first
|
||||
data = queryset.values(
|
||||
'image',
|
||||
).annotate(count=Count('image')).order_by('-count')
|
||||
|
||||
return Response(data)
|
||||
|
||||
|
||||
class PartDetail(generics.RetrieveUpdateAPIView):
|
||||
""" API endpoint for detail view of a single Part object """
|
||||
|
||||
queryset = Part.objects.all()
|
||||
serializer_class = PartSerializer
|
||||
serializer_class = part_serializers.PartSerializer
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
@ -104,12 +124,12 @@ class PartList(generics.ListCreateAPIView):
|
||||
- POST: Create a new Part object
|
||||
"""
|
||||
|
||||
serializer_class = PartSerializer
|
||||
serializer_class = part_serializers.PartSerializer
|
||||
|
||||
def list(self, request, *args, **kwargs):
|
||||
"""
|
||||
Instead of using the DRF serialiser to LIST,
|
||||
we serialize the objects manuually.
|
||||
we serialize the objects manually.
|
||||
This turns out to be significantly faster.
|
||||
"""
|
||||
|
||||
@ -218,7 +238,7 @@ class PartStarDetail(generics.RetrieveDestroyAPIView):
|
||||
""" API endpoint for viewing or removing a PartStar object """
|
||||
|
||||
queryset = PartStar.objects.all()
|
||||
serializer_class = PartStarSerializer
|
||||
serializer_class = part_serializers.PartStarSerializer
|
||||
|
||||
|
||||
class PartStarList(generics.ListCreateAPIView):
|
||||
@ -229,7 +249,7 @@ class PartStarList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
queryset = PartStar.objects.all()
|
||||
serializer_class = PartStarSerializer
|
||||
serializer_class = part_serializers.PartStarSerializer
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
|
||||
@ -271,7 +291,7 @@ class PartParameterTemplateList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
queryset = PartParameterTemplate.objects.all()
|
||||
serializer_class = PartParameterTemplateSerializer
|
||||
serializer_class = part_serializers.PartParameterTemplateSerializer
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
@ -294,7 +314,7 @@ class PartParameterList(generics.ListCreateAPIView):
|
||||
"""
|
||||
|
||||
queryset = PartParameter.objects.all()
|
||||
serializer_class = PartParameterSerializer
|
||||
serializer_class = part_serializers.PartParameterSerializer
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
@ -317,7 +337,7 @@ class BomList(generics.ListCreateAPIView):
|
||||
- POST: Create a new BomItem object
|
||||
"""
|
||||
|
||||
serializer_class = BomItemSerializer
|
||||
serializer_class = part_serializers.BomItemSerializer
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
|
||||
@ -360,7 +380,7 @@ class BomDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
""" API endpoint for detail view of a single BomItem object """
|
||||
|
||||
queryset = BomItem.objects.all()
|
||||
serializer_class = BomItemSerializer
|
||||
serializer_class = part_serializers.BomItemSerializer
|
||||
|
||||
permission_classes = [
|
||||
permissions.IsAuthenticated,
|
||||
@ -424,6 +444,8 @@ part_api_urls = [
|
||||
url(r'^star/', include(part_star_api_urls)),
|
||||
url(r'^parameter/', include(part_param_api_urls)),
|
||||
|
||||
url(r'^thumbs/', PartThumbs.as_view(), name='api-part-thumbs'),
|
||||
|
||||
url(r'^(?P<pk>\d+)/?', PartDetail.as_view(), name='api-part-detail'),
|
||||
|
||||
url(r'^.*$', PartList.as_view(), name='api-part-list'),
|
||||
|
@ -12,7 +12,6 @@ from django.core.exceptions import ValidationError
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Sum
|
||||
from django.db.models import prefetch_related_objects
|
||||
@ -24,6 +23,8 @@ from django.dispatch import receiver
|
||||
|
||||
from markdownx.models import MarkdownxField
|
||||
|
||||
from django_cleanup import cleanup
|
||||
|
||||
from mptt.models import TreeForeignKey
|
||||
|
||||
from datetime import datetime
|
||||
@ -136,18 +137,9 @@ def rename_part_image(instance, filename):
|
||||
"""
|
||||
|
||||
base = 'part_images'
|
||||
fname = os.path.basename(filename)
|
||||
|
||||
if filename.count('.') > 0:
|
||||
ext = filename.split('.')[-1]
|
||||
else:
|
||||
ext = ''
|
||||
|
||||
fn = 'part_{pk}_img'.format(pk=instance.pk)
|
||||
|
||||
if ext:
|
||||
fn += '.' + ext
|
||||
|
||||
return os.path.join(base, fn)
|
||||
return os.path.join(base, fname)
|
||||
|
||||
|
||||
def match_part_names(match, threshold=80, reverse=True, compare_length=False):
|
||||
@ -201,6 +193,7 @@ def match_part_names(match, threshold=80, reverse=True, compare_length=False):
|
||||
return matches
|
||||
|
||||
|
||||
@cleanup.ignore
|
||||
class Part(models.Model):
|
||||
""" The Part object represents an abstract part, the 'concept' of an actual entity.
|
||||
|
||||
@ -237,6 +230,26 @@ class Part(models.Model):
|
||||
verbose_name = "Part"
|
||||
verbose_name_plural = "Parts"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Overrides the save() function for the Part model.
|
||||
If the part image has been updated,
|
||||
then check if the "old" (previous) image is still used by another part.
|
||||
If not, it is considered "orphaned" and will be deleted.
|
||||
"""
|
||||
|
||||
if self.pk:
|
||||
previous = Part.objects.get(pk=self.pk)
|
||||
|
||||
if previous.image and not self.image == previous.image:
|
||||
# Are there any (other) parts which reference the image?
|
||||
n_refs = Part.objects.filter(image=previous.image).exclude(pk=self.pk).count()
|
||||
|
||||
if n_refs == 0:
|
||||
previous.image.delete(save=False)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return "{n} - {d}".format(n=self.full_name, d=self.description)
|
||||
|
||||
@ -832,10 +845,8 @@ class Part(models.Model):
|
||||
# Copy the part image
|
||||
if kwargs.get('image', True):
|
||||
if other.image:
|
||||
image_file = ContentFile(other.image.read())
|
||||
image_file.name = rename_part_image(self, other.image.url)
|
||||
|
||||
self.image = image_file
|
||||
# Reference the other image from this Part
|
||||
self.image = other.image
|
||||
|
||||
# Copy the BOM data
|
||||
if kwargs.get('bom', False):
|
||||
|
@ -30,6 +30,16 @@ class CategorySerializer(InvenTreeModelSerializer):
|
||||
]
|
||||
|
||||
|
||||
class PartThumbSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for the 'image' field of the Part model.
|
||||
Used to serve and display existing Part images.
|
||||
"""
|
||||
|
||||
image = serializers.URLField(read_only=True)
|
||||
count = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
||||
class PartBriefSerializer(InvenTreeModelSerializer):
|
||||
""" Serializer for Part (brief detail) """
|
||||
|
||||
|
@ -25,17 +25,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<div class='dropzone' id='part-thumb'>
|
||||
<img class="part-thumb"
|
||||
{% if part.image %}
|
||||
src="{{ part.image.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}/>
|
||||
</div>
|
||||
</div>
|
||||
{% include "part/part_thumb.html" %}
|
||||
<div class="media-body">
|
||||
<h4>
|
||||
{{ part.full_name }}
|
||||
@ -163,7 +153,7 @@
|
||||
|
||||
enableDragAndDrop(
|
||||
'#part-thumb',
|
||||
"{% url 'part-image' part.id %}",
|
||||
"{% url 'part-image-upload' part.id %}",
|
||||
{
|
||||
label: 'image',
|
||||
success: function(data, status, xhr) {
|
||||
@ -208,13 +198,54 @@
|
||||
});
|
||||
});
|
||||
|
||||
$("#part-thumb").click(function() {
|
||||
launchModalForm(
|
||||
"{% url 'part-image' part.id %}",
|
||||
$("#part-image-upload").click(function() {
|
||||
launchModalForm("{% url 'part-image-upload' part.id %}",
|
||||
{
|
||||
reload: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
function onSelectImage(response) {
|
||||
// Callback when the image-selection modal form is displayed
|
||||
// Populate the form with image data (requested via AJAX)
|
||||
|
||||
$("#modal-form").find("#image-select-table").bootstrapTable({
|
||||
pagination: true,
|
||||
pageSize: 25,
|
||||
url: "{% url 'api-part-thumbs' %}",
|
||||
showHeader: false,
|
||||
clickToSelect: true,
|
||||
singleSelect: true,
|
||||
columns: [
|
||||
{
|
||||
checkbox: true,
|
||||
},
|
||||
{
|
||||
field: 'image',
|
||||
title: 'Image',
|
||||
formatter: function(value, row, index, field) {
|
||||
return "<img src='/media/" + value + "' class='grid-image'/>"
|
||||
}
|
||||
}
|
||||
],
|
||||
onCheck: function(row, element) {
|
||||
|
||||
// Update the selected image in the form
|
||||
var ipt = $("#modal-form").find("#image-input");
|
||||
ipt.val(row.image);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#part-image-select").click(function() {
|
||||
launchModalForm("{% url 'part-image-select' part.id %}",
|
||||
{
|
||||
reload: true
|
||||
}
|
||||
);
|
||||
reload: true,
|
||||
after_render: onSelectImage
|
||||
});
|
||||
});
|
||||
|
||||
$("#part-edit").click(function() {
|
||||
|
20
InvenTree/part/templates/part/part_thumb.html
Normal file
20
InvenTree/part/templates/part/part_thumb.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class="media">
|
||||
<div class="media-left part-thumb-container">
|
||||
<div class='dropzone' id='part-thumb'>
|
||||
<img class="part-thumb"
|
||||
{% if part.image %}
|
||||
src="{{ part.image.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}/>
|
||||
</div>
|
||||
<div class='btn-row part-thumb-overlay'>
|
||||
<div class='btn-group'>
|
||||
<button type='button' class='btn btn-default btn-glyph' title="{% trans 'Select from existing images' %}" id='part-image-select'><span class='glyphicon glyphicon-th'></span></button>
|
||||
<button type='button' class='btn btn-default btn-glyph' title="{% trans 'Upload new image' %}" id='part-image-upload'><span class='glyphicon glyphicon-upload'></span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
21
InvenTree/part/templates/part/select_image.html
Normal file
21
InvenTree/part/templates/part/select_image.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends "modal_form.html" %}
|
||||
|
||||
{% block pre_form_content %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block form %}
|
||||
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
|
||||
{% csrf_token %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
<input id='image-input' name='image' type='hidden' value="{{ part.image }}">
|
||||
|
||||
<table id='image-select-table' class='table table-striped table-condensed table-img-grid'>
|
||||
</table>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
@ -23,7 +23,7 @@ class TemplateTagTest(TestCase):
|
||||
|
||||
def test_hash(self):
|
||||
hash = inventree_extras.inventree_commit_hash()
|
||||
self.assertEqual(len(hash), 7)
|
||||
self.assertGreater(len(hash), 5)
|
||||
|
||||
def test_date(self):
|
||||
d = inventree_extras.inventree_commit_date()
|
||||
@ -68,11 +68,8 @@ class PartTest(TestCase):
|
||||
|
||||
def test_rename_img(self):
|
||||
img = rename_part_image(self.R1, 'hello.png')
|
||||
self.assertEqual(img, os.path.join('part_images', 'part_3_img.png'))
|
||||
|
||||
img = rename_part_image(self.R2, 'test')
|
||||
self.assertEqual(img, os.path.join('part_images', 'part_4_img'))
|
||||
|
||||
self.assertEqual(img, os.path.join('part_images', 'hello.png'))
|
||||
|
||||
def test_stock(self):
|
||||
# No stock of any resistors
|
||||
res = Part.objects.filter(description__contains='resistor')
|
||||
|
@ -57,7 +57,8 @@ part_detail_urls = [
|
||||
url(r'^qr_code/?', views.PartQRCode.as_view(), name='part-qr'),
|
||||
|
||||
# Normal thumbnail with form
|
||||
url(r'^thumbnail/?', views.PartImage.as_view(), name='part-image'),
|
||||
url(r'^thumbnail/?', views.PartImageUpload.as_view(), name='part-image-upload'),
|
||||
url(r'^thumb-select/?', views.PartImageSelect.as_view(), name='part-image-select'),
|
||||
|
||||
# Any other URLs go to the part detail page
|
||||
url(r'^.*$', views.PartDetail.as_view(), name='part-detail'),
|
||||
|
@ -14,6 +14,9 @@ from django.urls import reverse, reverse_lazy
|
||||
from django.views.generic import DetailView, ListView, FormView, UpdateView
|
||||
from django.forms.models import model_to_dict
|
||||
from django.forms import HiddenInput, CheckboxInput
|
||||
from django.conf import settings
|
||||
|
||||
import os
|
||||
|
||||
from fuzzywuzzy import fuzz
|
||||
from decimal import Decimal
|
||||
@ -74,7 +77,7 @@ class PartAttachmentCreate(AjaxCreateView):
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': 'Added attachment'
|
||||
'success': _('Added attachment')
|
||||
}
|
||||
|
||||
def get_initial(self):
|
||||
@ -116,7 +119,7 @@ class PartAttachmentEdit(AjaxUpdateView):
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': 'Part attachment updated'
|
||||
'success': _('Part attachment updated')
|
||||
}
|
||||
|
||||
def get_form(self):
|
||||
@ -303,7 +306,7 @@ class PartDuplicate(AjaxCreateView):
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': 'Copied part'
|
||||
'success': _('Copied part')
|
||||
}
|
||||
|
||||
def get_part_to_copy(self):
|
||||
@ -411,12 +414,12 @@ class PartCreate(AjaxCreateView):
|
||||
model = Part
|
||||
form_class = part_forms.EditPartForm
|
||||
|
||||
ajax_form_title = 'Create new part'
|
||||
ajax_form_title = _('Create new part')
|
||||
ajax_template_name = 'part/create_part.html'
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': "Created new part",
|
||||
'success': _("Created new part"),
|
||||
}
|
||||
|
||||
def get_category_id(self):
|
||||
@ -601,27 +604,66 @@ class PartQRCode(QRCodeView):
|
||||
return None
|
||||
|
||||
|
||||
class PartImage(AjaxUpdateView):
|
||||
""" View for uploading Part image """
|
||||
class PartImageUpload(AjaxUpdateView):
|
||||
""" View for uploading a new Part image """
|
||||
|
||||
model = Part
|
||||
ajax_template_name = 'modal_form.html'
|
||||
ajax_form_title = 'Upload Part Image'
|
||||
ajax_form_title = _('Upload Part Image')
|
||||
form_class = part_forms.PartImageForm
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'success': 'Updated part image',
|
||||
'success': _('Updated part image'),
|
||||
}
|
||||
|
||||
|
||||
class PartImageSelect(AjaxUpdateView):
|
||||
""" View for selecting Part image from existing images. """
|
||||
|
||||
model = Part
|
||||
ajax_template_name = 'part/select_image.html'
|
||||
ajax_form_title = _('Select Part Image')
|
||||
|
||||
fields = [
|
||||
'image',
|
||||
]
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
part = self.get_object()
|
||||
form = self.get_form()
|
||||
|
||||
img = request.POST.get('image', '')
|
||||
|
||||
img = os.path.basename(img)
|
||||
|
||||
data = {}
|
||||
|
||||
if img:
|
||||
img_path = os.path.join(settings.MEDIA_ROOT, 'part_images', img)
|
||||
|
||||
# Ensure that the image already exists
|
||||
if os.path.exists(img_path):
|
||||
|
||||
part.image = os.path.join('part_images', img)
|
||||
part.save()
|
||||
|
||||
data['success'] = _('Updated part image')
|
||||
|
||||
if 'success' not in data:
|
||||
data['error'] = _('Part image not found')
|
||||
|
||||
return self.renderJsonResponse(request, form, data)
|
||||
|
||||
|
||||
class PartEdit(AjaxUpdateView):
|
||||
""" View for editing Part object """
|
||||
|
||||
model = Part
|
||||
form_class = part_forms.EditPartForm
|
||||
ajax_template_name = 'modal_form.html'
|
||||
ajax_form_title = 'Edit Part Properties'
|
||||
ajax_form_title = _('Edit Part Properties')
|
||||
context_object_name = 'part'
|
||||
|
||||
def get_form(self):
|
||||
@ -643,7 +685,7 @@ class BomValidate(AjaxUpdateView):
|
||||
""" Modal form view for validating a part BOM """
|
||||
|
||||
model = Part
|
||||
ajax_form_title = "Validate BOM"
|
||||
ajax_form_title = _("Validate BOM")
|
||||
ajax_template_name = 'part/bom_validate.html'
|
||||
context_object_name = 'part'
|
||||
form_class = part_forms.BomValidateForm
|
||||
@ -1308,14 +1350,14 @@ class PartDelete(AjaxDeleteView):
|
||||
|
||||
model = Part
|
||||
ajax_template_name = 'part/partial_delete.html'
|
||||
ajax_form_title = 'Confirm Part Deletion'
|
||||
ajax_form_title = _('Confirm Part Deletion')
|
||||
context_object_name = 'part'
|
||||
|
||||
success_url = '/part/'
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'danger': 'Part was deleted',
|
||||
'danger': _('Part was deleted'),
|
||||
}
|
||||
|
||||
|
||||
@ -1324,7 +1366,7 @@ class PartPricing(AjaxView):
|
||||
|
||||
model = Part
|
||||
ajax_template_name = "part/part_pricing.html"
|
||||
ajax_form_title = "Part Pricing"
|
||||
ajax_form_title = _("Part Pricing")
|
||||
form_class = part_forms.PartPriceForm
|
||||
|
||||
def get_part(self):
|
||||
@ -1446,7 +1488,7 @@ class PartParameterTemplateCreate(AjaxCreateView):
|
||||
|
||||
model = PartParameterTemplate
|
||||
form_class = part_forms.EditPartParameterTemplateForm
|
||||
ajax_form_title = 'Create Part Parameter Template'
|
||||
ajax_form_title = _('Create Part Parameter Template')
|
||||
|
||||
|
||||
class PartParameterTemplateEdit(AjaxUpdateView):
|
||||
@ -1454,14 +1496,14 @@ class PartParameterTemplateEdit(AjaxUpdateView):
|
||||
|
||||
model = PartParameterTemplate
|
||||
form_class = part_forms.EditPartParameterTemplateForm
|
||||
ajax_form_title = 'Edit Part Parameter Template'
|
||||
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"
|
||||
ajax_form_title = _("Delete Part Parameter Template")
|
||||
|
||||
|
||||
class PartParameterCreate(AjaxCreateView):
|
||||
@ -1469,7 +1511,7 @@ class PartParameterCreate(AjaxCreateView):
|
||||
|
||||
model = PartParameter
|
||||
form_class = part_forms.EditPartParameterForm
|
||||
ajax_form_title = 'Create Part Parameter'
|
||||
ajax_form_title = _('Create Part Parameter')
|
||||
|
||||
def get_initial(self):
|
||||
|
||||
@ -1519,7 +1561,7 @@ class PartParameterEdit(AjaxUpdateView):
|
||||
|
||||
model = PartParameter
|
||||
form_class = part_forms.EditPartParameterForm
|
||||
ajax_form_title = 'Edit Part Parameter'
|
||||
ajax_form_title = _('Edit Part Parameter')
|
||||
|
||||
def get_form(self):
|
||||
|
||||
@ -1533,7 +1575,7 @@ class PartParameterDelete(AjaxDeleteView):
|
||||
|
||||
model = PartParameter
|
||||
ajax_template_name = 'part/param_delete.html'
|
||||
ajax_form_title = 'Delete Part Parameter'
|
||||
ajax_form_title = _('Delete Part Parameter')
|
||||
|
||||
|
||||
class CategoryDetail(DetailView):
|
||||
@ -1549,7 +1591,7 @@ class CategoryEdit(AjaxUpdateView):
|
||||
model = PartCategory
|
||||
form_class = part_forms.EditCategoryForm
|
||||
ajax_template_name = 'modal_form.html'
|
||||
ajax_form_title = 'Edit Part Category'
|
||||
ajax_form_title = _('Edit Part Category')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CategoryEdit, self).get_context_data(**kwargs).copy()
|
||||
@ -1584,13 +1626,13 @@ class CategoryDelete(AjaxDeleteView):
|
||||
""" Delete view to delete a PartCategory """
|
||||
model = PartCategory
|
||||
ajax_template_name = 'part/category_delete.html'
|
||||
ajax_form_title = 'Delete Part Category'
|
||||
ajax_form_title = _('Delete Part Category')
|
||||
context_object_name = 'category'
|
||||
success_url = '/part/'
|
||||
|
||||
def get_data(self):
|
||||
return {
|
||||
'danger': 'Part category was deleted',
|
||||
'danger': _('Part category was deleted'),
|
||||
}
|
||||
|
||||
|
||||
@ -1598,7 +1640,7 @@ class CategoryCreate(AjaxCreateView):
|
||||
""" Create view to make a new PartCategory """
|
||||
model = PartCategory
|
||||
ajax_form_action = reverse_lazy('category-create')
|
||||
ajax_form_title = 'Create new part category'
|
||||
ajax_form_title = _('Create new part category')
|
||||
ajax_template_name = 'modal_form.html'
|
||||
form_class = part_forms.EditCategoryForm
|
||||
|
||||
@ -1649,7 +1691,7 @@ class BomItemCreate(AjaxCreateView):
|
||||
model = BomItem
|
||||
form_class = part_forms.EditBomItemForm
|
||||
ajax_template_name = 'modal_form.html'
|
||||
ajax_form_title = 'Create BOM item'
|
||||
ajax_form_title = _('Create BOM item')
|
||||
|
||||
def get_form(self):
|
||||
""" Override get_form() method to reduce Part selection options.
|
||||
@ -1715,7 +1757,7 @@ class BomItemEdit(AjaxUpdateView):
|
||||
model = BomItem
|
||||
form_class = part_forms.EditBomItemForm
|
||||
ajax_template_name = 'modal_form.html'
|
||||
ajax_form_title = 'Edit BOM item'
|
||||
ajax_form_title = _('Edit BOM item')
|
||||
|
||||
def get_form(self):
|
||||
""" Override get_form() method to filter part selection options
|
||||
@ -1763,4 +1805,4 @@ class BomItemDelete(AjaxDeleteView):
|
||||
model = BomItem
|
||||
ajax_template_name = 'part/bom-delete.html'
|
||||
context_object_name = 'item'
|
||||
ajax_form_title = 'Confim BOM item deletion'
|
||||
ajax_form_title = _('Confim BOM item deletion')
|
||||
|
@ -44,7 +44,6 @@ class CreateStockItemForm(HelperForm):
|
||||
'serial_numbers',
|
||||
'delete_on_deplete',
|
||||
'status',
|
||||
'notes',
|
||||
'URL',
|
||||
]
|
||||
|
||||
|
19
InvenTree/stock/migrations/0020_auto_20200206_1213.py
Normal file
19
InvenTree/stock/migrations/0020_auto_20200206_1213.py
Normal file
@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.9 on 2020-02-06 12:13
|
||||
|
||||
from django.db import migrations
|
||||
import markdownx.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('stock', '0019_auto_20200202_1024'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='stockitem',
|
||||
name='notes',
|
||||
field=markdownx.models.MarkdownxField(blank=True, help_text='Stock Item Notes', null=True),
|
||||
),
|
||||
]
|
@ -360,7 +360,7 @@ class StockItem(models.Model):
|
||||
choices=StockStatus.items(),
|
||||
validators=[MinValueValidator(0)])
|
||||
|
||||
notes = MarkdownxField(blank=True, help_text=_('Stock Item Notes'))
|
||||
notes = MarkdownxField(blank=True, null=True, help_text=_('Stock Item Notes'))
|
||||
|
||||
# If stock item is incoming, an (optional) ETA field
|
||||
# expected_arrival = models.DateField(null=True, blank=True)
|
||||
|
@ -884,34 +884,49 @@ class StockItemCreate(AjaxCreateView):
|
||||
form.errors['serial_numbers'] = [_('The following serial numbers already exist: ({sn})'.format(sn=exists))]
|
||||
valid = False
|
||||
|
||||
# At this point we have a list of serial numbers which we know are valid,
|
||||
# and do not currently exist
|
||||
form.clean()
|
||||
else:
|
||||
# At this point we have a list of serial numbers which we know are valid,
|
||||
# and do not currently exist
|
||||
form.clean()
|
||||
|
||||
data = form.cleaned_data
|
||||
form_data = form.cleaned_data
|
||||
|
||||
for serial in serials:
|
||||
# Create a new stock item for each serial number
|
||||
item = StockItem(
|
||||
part=part,
|
||||
quantity=1,
|
||||
serial=serial,
|
||||
supplier_part=data.get('supplier_part'),
|
||||
location=data.get('location'),
|
||||
batch=data.get('batch'),
|
||||
delete_on_deplete=False,
|
||||
status=data.get('status'),
|
||||
notes=data.get('notes'),
|
||||
URL=data.get('URL'),
|
||||
)
|
||||
for serial in serials:
|
||||
# Create a new stock item for each serial number
|
||||
item = StockItem(
|
||||
part=part,
|
||||
quantity=1,
|
||||
serial=serial,
|
||||
supplier_part=form_data.get('supplier_part'),
|
||||
location=form_data.get('location'),
|
||||
batch=form_data.get('batch'),
|
||||
delete_on_deplete=False,
|
||||
status=form_data.get('status'),
|
||||
URL=form_data.get('URL'),
|
||||
)
|
||||
|
||||
item.save(user=request.user)
|
||||
item.save(user=request.user)
|
||||
|
||||
data['success'] = _('Created {n} new stock items'.format(n=len(serials)))
|
||||
valid = True
|
||||
|
||||
except ValidationError as e:
|
||||
form.errors['serial_numbers'] = e.messages
|
||||
valid = False
|
||||
|
||||
else:
|
||||
else:
|
||||
# We have a serialized part, but no serial numbers specified...
|
||||
form.clean()
|
||||
form._post_clean()
|
||||
|
||||
item = form.save(commit=False)
|
||||
item.save(user=request.user)
|
||||
|
||||
data['pk'] = item.pk
|
||||
data['url'] = item.get_absolute_url()
|
||||
data['success'] = _("Created new stock item")
|
||||
|
||||
else: # Referenced Part object is not marked as "trackable"
|
||||
# For non-serialized items, simply save the form.
|
||||
# We need to call _post_clean() here because it is prevented in the form implementation
|
||||
form.clean()
|
||||
|
Loading…
Reference in New Issue
Block a user