2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-08-10 05:40:55 +00:00

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
14 changed files with 182 additions and 127 deletions

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>