Company attachment (#4346)

* Adds new model, API, serializers, etc

- Refactor InvenTreeAttachmentSerializer class
- Reduces code duplication

* Update front end

* Increment API version
This commit is contained in:
Oliver 2023-02-16 09:52:13 +11:00 committed by GitHub
parent f4bc65523c
commit 45d50fc618
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 182 additions and 127 deletions

View File

@ -2,11 +2,14 @@
# InvenTree API version
INVENTREE_API_VERSION = 94
INVENTREE_API_VERSION = 95
"""
Increment this API version number whenever there is a significant change to the API that any clients need to know about
v95 -> 2023-02-16 : https://github.com/inventree/InvenTree/pull/4346
- Adds "CompanyAttachment" model (and associated API endpoints)
v94 -> 2023-02-10 : https://github.com/inventree/InvenTree/pull/4327
- Adds API endpoints for the "Group" auth model

View File

@ -282,6 +282,25 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
The only real addition here is that we support "renaming" of the attachment file.
"""
@staticmethod
def attachment_fields(extra_fields=None):
"""Default set of fields for an attachment serializer"""
fields = [
'pk',
'attachment',
'filename',
'link',
'comment',
'upload_date',
'user',
'user_detail',
]
if extra_fields:
fields += extra_fields
return fields
user_detail = UserSerializer(source='user', read_only=True, many=False)
attachment = InvenTreeAttachmentSerializerField(
@ -297,6 +316,8 @@ class InvenTreeAttachmentSerializer(InvenTreeModelSerializer):
allow_blank=False,
)
upload_date = serializers.DateField(read_only=True)
class InvenTreeImageSerializerField(serializers.ImageField):
"""Custom image serializer.

View File

@ -929,18 +929,6 @@ class BuildAttachmentSerializer(InvenTreeAttachmentSerializer):
"""Serializer metaclass"""
model = BuildOrderAttachment
fields = [
'pk',
fields = InvenTreeAttachmentSerializer.attachment_fields([
'build',
'attachment',
'link',
'filename',
'comment',
'upload_date',
'user',
'user_detail',
]
read_only_fields = [
'upload_date',
]
])

View File

@ -15,10 +15,10 @@ from InvenTree.mixins import (ListCreateAPI, RetrieveUpdateAPI,
RetrieveUpdateDestroyAPI)
from plugin.serializers import MetadataSerializer
from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
ManufacturerPartParameter, SupplierPart,
SupplierPriceBreak)
from .serializers import (CompanySerializer,
from .models import (Company, CompanyAttachment, ManufacturerPart,
ManufacturerPartAttachment, ManufacturerPartParameter,
SupplierPart, SupplierPriceBreak)
from .serializers import (CompanyAttachmentSerializer, CompanySerializer,
ManufacturerPartAttachmentSerializer,
ManufacturerPartParameterSerializer,
ManufacturerPartSerializer, SupplierPartSerializer,
@ -96,6 +96,28 @@ class CompanyMetadata(RetrieveUpdateAPI):
queryset = Company.objects.all()
class CompanyAttachmentList(AttachmentMixin, ListCreateDestroyAPIView):
"""API endpoint for the CompanyAttachment model"""
queryset = CompanyAttachment.objects.all()
serializer_class = CompanyAttachmentSerializer
filter_backends = [
DjangoFilterBackend,
]
filterset_fields = [
'company',
]
class CompanyAttachmentDetail(AttachmentMixin, RetrieveUpdateDestroyAPI):
"""Detail endpoint for CompanyAttachment model."""
queryset = CompanyAttachment.objects.all()
serializer_class = CompanyAttachmentSerializer
class ManufacturerPartFilter(rest_filters.FilterSet):
"""Custom API filters for the ManufacturerPart list endpoint."""
@ -521,6 +543,11 @@ company_api_urls = [
re_path(r'^.*$', CompanyDetail.as_view(), name='api-company-detail'),
])),
re_path(r'^attachment/', include([
re_path(r'^(?P<pk>\d+)/', CompanyAttachmentDetail.as_view(), name='api-company-attachment-detail'),
re_path(r'^$', CompanyAttachmentList.as_view(), name='api-company-attachment-list'),
])),
re_path(r'^.*$', CompanyList.as_view(), name='api-company-list'),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.16 on 2023-02-15 12:55
import InvenTree.fields
import InvenTree.models
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('company', '0053_supplierpart_updated'),
]
operations = [
migrations.CreateModel(
name='CompanyAttachment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('attachment', models.FileField(blank=True, help_text='Select file to attach', null=True, upload_to=InvenTree.models.rename_attachment, verbose_name='Attachment')),
('link', InvenTree.fields.InvenTreeURLField(blank=True, help_text='Link to external URL', null=True, verbose_name='Link')),
('comment', models.CharField(blank=True, help_text='File comment', max_length=100, verbose_name='Comment')),
('upload_date', models.DateField(auto_now_add=True, null=True, verbose_name='upload date')),
('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='company.company', verbose_name='Company')),
('user', models.ForeignKey(blank=True, help_text='User', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='User')),
],
options={
'abstract': False,
},
),
]

View File

@ -205,6 +205,25 @@ class Company(MetadataMixin, models.Model):
return stock.objects.filter(Q(supplier_part__supplier=self.id) | Q(supplier_part__manufacturer_part__manufacturer=self.id)).all()
class CompanyAttachment(InvenTreeAttachment):
"""Model for storing file or URL attachments against a Company object"""
@staticmethod
def get_api_url():
"""Return the API URL associated with this model"""
return reverse('api-company-attachment-list')
def getSubdir(self):
"""Return the subdirectory where these attachments are uploaded"""
return os.path.join('company_files', str(self.company.pk))
company = models.ForeignKey(
Company, on_delete=models.CASCADE,
verbose_name=_('Company'),
related_name='attachments',
)
class Contact(models.Model):
"""A Contact represents a person who works at a particular company. A Company may have zero or more associated Contact objects.

View File

@ -17,9 +17,9 @@ from InvenTree.serializers import (InvenTreeAttachmentSerializer,
InvenTreeMoneySerializer, RemoteImageMixin)
from part.serializers import PartBriefSerializer
from .models import (Company, ManufacturerPart, ManufacturerPartAttachment,
ManufacturerPartParameter, SupplierPart,
SupplierPriceBreak)
from .models import (Company, CompanyAttachment, ManufacturerPart,
ManufacturerPartAttachment, ManufacturerPartParameter,
SupplierPart, SupplierPriceBreak)
class CompanyBriefSerializer(InvenTreeModelSerializer):
@ -126,6 +126,18 @@ class CompanySerializer(RemoteImageMixin, InvenTreeModelSerializer):
return self.instance
class CompanyAttachmentSerializer(InvenTreeAttachmentSerializer):
"""Serializer for the CompanyAttachment class"""
class Meta:
"""Metaclass defines serializer options"""
model = CompanyAttachment
fields = InvenTreeAttachmentSerializer.attachment_fields([
'company',
])
class ManufacturerPartSerializer(InvenTreeModelSerializer):
"""Serializer for ManufacturerPart object."""
@ -179,21 +191,9 @@ class ManufacturerPartAttachmentSerializer(InvenTreeAttachmentSerializer):
model = ManufacturerPartAttachment
fields = [
'pk',
fields = InvenTreeAttachmentSerializer.attachment_fields([
'manufacturer_part',
'attachment',
'filename',
'link',
'comment',
'upload_date',
'user',
'user_detail',
]
read_only_fields = [
'upload_date',
]
])
class ManufacturerPartParameterSerializer(InvenTreeModelSerializer):

View File

@ -194,11 +194,54 @@
</div>
</div>
<div class='panel panel-hidden' id='panel-attachments'>
<div class='panel-heading'>
<div class='d-flex flex-wrap'>
<h4>{% trans "Attachments" %}</h4>
{% include "spacer.html" %}
<div class='btn-group' role='group'>
{% include "attachment_button.html" %}
</div>
</div>
</div>
<div class='panel-content'>
{% include "attachment_table.html" %}
</div>
</div>
{% endblock %}
{% block js_ready %}
{{ block.super }}
onPanelLoad("attachments", function() {
loadAttachmentTable('{% url "api-company-attachment-list" %}', {
filters: {
company: {{ company.pk }},
},
fields: {
company: {
value: {{ company.pk }},
hidden: true
}
}
});
enableDragAndDrop(
'#attachment-dropzone',
'{% url "api-company-attachment-list" %}',
{
data: {
company: {{ company.id }},
},
label: 'attachment',
success: function(data, status, xhr) {
reloadAttachmentTable();
}
}
);
});
onPanelLoad('company-notes', function() {
setupNotesField(

View File

@ -24,3 +24,5 @@
{% endif %}
{% trans "Notes" as text %}
{% include "sidebar_item.html" with label='company-notes' text=text icon="fa-clipboard" %}
{% trans "Attachments" as text %}
{% include "sidebar_item.html" with label='attachments' text=text icon="fa-paperclip" %}

View File

@ -1,33 +0,0 @@
{% load i18n %}
{% load inventree_extras %}
<ul class='list-group'>
<li class='list-group-item'>
<a href='#' id='supplier-part-menu-toggle'>
<span class='menu-tab-icon fas fa-expand-arrows-alt'></span>
</a>
</li>
<li class='list-group-item' title='{% trans "Supplier Part Stock" %}'>
<a href='#' id='select-stock' class='nav-toggle'>
<span class='fas fa-boxes sidebar-icon'></span>
{% trans "Stock" %}
</a>
</li>
<li class='list-group-item' title='{% trans "Supplier Part Orders" %}'>
<a href='#' id='select-purchase-orders' class='nav-toggle'>
<span class='fas fa-shopping-cart sidebar-icon'></span>
{% trans "Orders" %}
</a>
</li>
<li class='list-group-item' title='{% trans "Supplier Part Pricing" %}'>
<a href='#' id='select-pricing' class='nav-toggle'>
<span class='fas fa-dollar-sign sidebar-icon'></span>
{% trans "Pricing" %}
</a>
</li>
</ul>

View File

@ -644,21 +644,9 @@ class PurchaseOrderAttachmentSerializer(InvenTreeAttachmentSerializer):
model = order.models.PurchaseOrderAttachment
fields = [
'pk',
fields = InvenTreeAttachmentSerializer.attachment_fields([
'order',
'attachment',
'link',
'filename',
'comment',
'upload_date',
'user',
'user_detail',
]
read_only_fields = [
'upload_date',
]
])
class SalesOrderSerializer(AbstractOrderSerializer, InvenTreeModelSerializer):
@ -1416,18 +1404,6 @@ class SalesOrderAttachmentSerializer(InvenTreeAttachmentSerializer):
model = order.models.SalesOrderAttachment
fields = [
'pk',
fields = InvenTreeAttachmentSerializer.attachment_fields([
'order',
'attachment',
'filename',
'link',
'comment',
'upload_date',
'user',
'user_detail',
]
read_only_fields = [
'upload_date',
]
])

View File

@ -106,21 +106,9 @@ class PartAttachmentSerializer(InvenTreeAttachmentSerializer):
"""Metaclass defining serializer fields"""
model = PartAttachment
fields = [
'pk',
fields = InvenTreeAttachmentSerializer.attachment_fields([
'part',
'attachment',
'filename',
'link',
'comment',
'upload_date',
'user',
'user_detail',
]
read_only_fields = [
'upload_date',
]
])
class PartTestTemplateSerializer(InvenTreeModelSerializer):

View File

@ -622,23 +622,9 @@ class StockItemAttachmentSerializer(InvenTree.serializers.InvenTreeAttachmentSer
model = StockItemAttachment
fields = [
'pk',
fields = InvenTree.serializers.InvenTreeAttachmentSerializer.attachment_fields([
'stock_item',
'attachment',
'filename',
'link',
'comment',
'upload_date',
'user',
'user_detail',
]
read_only_fields = [
'upload_date',
'user',
'user_detail'
]
])
class StockItemTestResultSerializer(InvenTree.serializers.InvenTreeModelSerializer):

View File

@ -130,6 +130,7 @@ class RuleSet(models.Model):
],
'purchase_order': [
'company_company',
'company_companyattachment',
'company_manufacturerpart',
'company_manufacturerpartparameter',
'company_supplierpart',
@ -142,6 +143,7 @@ class RuleSet(models.Model):
],
'sales_order': [
'company_company',
'company_companyattachment',
'order_salesorder',
'order_salesorderallocation',
'order_salesorderattachment',