Merge pull request #53 from inventree/stock-track

Stock track
This commit is contained in:
Oliver 2018-04-16 20:46:52 +10:00 committed by GitHub
commit 835144c87f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 191 additions and 639 deletions

View File

@ -48,9 +48,8 @@ INSTALLED_APPS = [
# InvenTree apps
'part.apps.PartConfig',
'supplier.apps.SupplierConfig',
'stock.apps.StockConfig',
'track.apps.TrackConfig',
'supplier.apps.SupplierConfig',
]
MIDDLEWARE = [

View File

@ -16,8 +16,6 @@ from django.conf.urls.static import static
from django.views.generic.base import RedirectView
from track.urls import tracking_urls
# from project.urls import prj_urls, prj_part_urls, prj_cat_urls, prj_run_urls
# from track.urls import unique_urls, part_track_urls
@ -70,7 +68,6 @@ urlpatterns = [
url(r'^part/', include(part_urls)),
url(r'^stock/', include(stock_urls)),
url(r'^supplier/', include(supplier_urls)),
url(r'^track/', include(tracking_urls)),
url(r'^admin/', admin.site.urls),
url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')),
@ -84,4 +81,4 @@ if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Send any unknown URLs to the parts page
urlpatterns += [url(r'^.*$', RedirectView.as_view(url='part/', permanent=False), name='part-index')]
urlpatterns += [url(r'^.*$', RedirectView.as_view(url='/part/', permanent=False), name='part-index')]

View File

@ -111,7 +111,8 @@ class Part(models.Model):
units = models.CharField(max_length=20, default="pcs", blank=True)
# Is this part "trackable"?
# Trackable parts can have unique instances which are assigned serial numbers
# Trackable parts can have unique instances
# which are assigned serial numbers (or batch numbers)
# and can have their movements tracked
trackable = models.BooleanField(default=False)

View File

@ -9,7 +9,6 @@
<li><a href="/part/">Parts</a></li>
<li><a href="/stock/">Stock</a></li>
<li><a href="/supplier/">Suppliers</a></li>
<li><a href="/track/">Tracking</a></li>
</ul>
</div>
</nav>

View File

@ -2,6 +2,7 @@ from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import StockLocation, StockItem
from .models import StockItemTracking
class LocationAdmin(admin.ModelAdmin):
@ -12,5 +13,10 @@ class StockItemAdmin(SimpleHistoryAdmin):
list_display = ('part', 'quantity', 'location', 'status', 'updated')
class StockTrackingAdmin(admin.ModelAdmin):
list_display = ('item', 'date', 'title')
admin.site.register(StockLocation, LocationAdmin)
admin.site.register(StockItem, StockItemAdmin)
admin.site.register(StockItemTracking, StockTrackingAdmin)

View File

@ -44,6 +44,10 @@ class EditStockItemForm(forms.ModelForm):
'part',
'supplier_part',
'location',
'belongs_to',
'serial',
'batch',
'quantity',
'status'
'status',
'customer'
]

View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-16 08:53
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('supplier', '0006_auto_20180415_1011'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('stock', '0006_auto_20180415_0302'),
]
operations = [
migrations.CreateModel(
name='StockItemTracking',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(auto_now_add=True)),
('title', models.CharField(max_length=250)),
('description', models.CharField(blank=True, max_length=1024)),
],
),
migrations.RemoveField(
model_name='historicalstockitem',
name='history_user',
),
migrations.RemoveField(
model_name='historicalstockitem',
name='location',
),
migrations.RemoveField(
model_name='historicalstockitem',
name='part',
),
migrations.RemoveField(
model_name='historicalstockitem',
name='stocktake_user',
),
migrations.RemoveField(
model_name='historicalstockitem',
name='supplier_part',
),
migrations.AddField(
model_name='stockitem',
name='batch',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='stockitem',
name='belongs_to',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='owned_parts', to='stock.StockItem'),
),
migrations.AddField(
model_name='stockitem',
name='customer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='stockitems', to='supplier.Customer'),
),
migrations.AddField(
model_name='stockitem',
name='serial',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.DeleteModel(
name='HistoricalStockItem',
),
migrations.AddField(
model_name='stockitemtracking',
name='item',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tracking_info', to='stock.StockItem'),
),
migrations.AddField(
model_name='stockitemtracking',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -3,9 +3,9 @@ from django.utils.translation import ugettext as _
from django.db import models, transaction
from django.core.validators import MinValueValidator
from django.contrib.auth.models import User
from simple_history.models import HistoricalRecords
from supplier.models import SupplierPart
from supplier.models import Customer
from part.models import Part
from InvenTree.models import InvenTreeTree
@ -50,19 +50,43 @@ def before_delete_stock_location(sender, instance, using, **kwargs):
class StockItem(models.Model):
"""
A 'StockItem' instance represents a quantity of physical instances of a part.
It may exist in a StockLocation, or as part of a sub-assembly installed into another StockItem
StockItems may be tracked using batch or serial numbers.
If a serial number is assigned, then StockItem cannot have a quantity other than 1
"""
def get_absolute_url(self):
return '/stock/item/{id}/'.format(id=self.id)
# The 'master' copy of the part of which this stock item is an instance
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='locations')
# The 'supplier part' used in this instance. May be null if no supplier parts are defined the master part
supplier_part = models.ForeignKey(SupplierPart, blank=True, null=True, on_delete=models.SET_NULL)
# Where the part is stored. If the part has been used to build another stock item, the location may not make sense
location = models.ForeignKey(StockLocation, on_delete=models.DO_NOTHING,
related_name='items', blank=True, null=True)
# If this StockItem belongs to another StockItem (e.g. as part of a sub-assembly)
belongs_to = models.ForeignKey('self', on_delete=models.DO_NOTHING,
related_name='owned_parts', blank=True, null=True)
# The StockItem may be assigned to a particular customer
customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, related_name='stockitems', blank=True, null=True)
# Optional serial number
serial = models.PositiveIntegerField(blank=True, null=True)
# Optional batch information
batch = models.CharField(max_length=100, blank=True)
# Quantity of this stock item. Value may be overridden by other settings
quantity = models.PositiveIntegerField(validators=[MinValueValidator(0)])
# Last time this item was updated (set automagically)
updated = models.DateField(auto_now=True)
# last time the stock was checked / counted
@ -97,8 +121,9 @@ class StockItem(models.Model):
infinite = models.BooleanField(default=False)
# History of this item
history = HistoricalRecords()
@property
def has_tracking_info(self):
return self.tracking_info.all().count() > 0
@transaction.atomic
def stocktake(self, count, user):
@ -147,3 +172,30 @@ class StockItem(models.Model):
n=self.quantity,
part=self.part.name,
loc=self.location.name)
class StockItemTracking(models.Model):
""" Stock tracking entry
"""
# Stock item
item = models.ForeignKey(StockItem, on_delete=models.CASCADE,
related_name='tracking_info')
# Date this entry was created (cannot be edited)
date = models.DateField(auto_now_add=True, editable=False)
# Short-form title for this tracking entry
title = models.CharField(max_length=250)
# Optional longer description
description = models.CharField(max_length=1024, blank=True)
# Which user created this tracking entry?
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
# TODO
# image = models.ImageField(upload_to=func, max_length=255, null=True, blank=True)
# TODO
# file = models.FileField()

View File

@ -11,10 +11,35 @@
<td>Part</td>
<td><a href="{% url 'part-stock' item.part.id %}">{{ item.part.name }}</td>
</tr>
{% if item.belongs_to %}
<tr>
<td>Belongs To</td>
<td><a href="{% url 'stock-item-detail' item.belongs_to.id %}">{{ item.belongs_to }}</a></td>
</tr>
{% elif item.location %}
<tr>
<td>Location</td>
<td><a href="{% url 'stock-location-detail' item.location.id %}">{{ item.location.name }}</a></td>
</tr>
{% endif %}
{% if item.serial %}
<tr>
<td>Serial</td>
<td>{{ item.serial }}</td>
</tr>
{% endif %}
{% if item.batch %}
<tr>
<td>Batch</td>
<td>{{ item.batch }}</td>
</tr>
{% endif %}
{% if item.customer %}
<tr>
<td>Customer</td>
<td>{{ item.customer.name }}</td>
</tr>
{% endif %}
<tr>
<td>Quantity</td>
<td>{{ item.quantity }}</td>
@ -47,6 +72,21 @@
{% endif %}
</table>
{% if item.has_tracking_info %}
<h3>Stock Tracking</h3>
<ul class='list-group'>
{% for track in item.tracking_info.all %}
<li class='list-group-item'>
<b>{{ track.title }}</b>
{% if track.description %}
<br><br>{{ track.description }}</i>
{% endif %}
<span class='badge'>{{ track.date }}</span>
</li>
{% endfor %}
</ul>
{% endif %}
<div class='container-fluid'>
<a href="{% url 'stock-item-edit' item.id %}">
<button class='btn btn-info'>Edit Stock Item</button>

View File

@ -1,15 +0,0 @@
from django.contrib import admin
from .models import UniquePart, PartTrackingInfo
class UniquePartAdmin(admin.ModelAdmin):
list_display = ('part', 'serial', 'status', 'creation_date')
class PartTrackingAdmin(admin.ModelAdmin):
list_display = ('part', 'date', 'title')
admin.site.register(UniquePart, UniquePartAdmin)
admin.site.register(PartTrackingInfo, PartTrackingAdmin)

View File

@ -1,97 +0,0 @@
from django_filters.rest_framework import FilterSet, DjangoFilterBackend
from django_filters import NumberFilter
from rest_framework import generics, permissions
from .models import UniquePart, PartTrackingInfo
from .serializers import UniquePartSerializer, PartTrackingInfoSerializer
class UniquePartDetail(generics.RetrieveUpdateDestroyAPIView):
"""
get:
Return a single UniquePart
post:
Update a UniquePart
delete:
Remove a UniquePart
"""
queryset = UniquePart.objects.all()
serializer_class = UniquePartSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class UniquePartFilter(FilterSet):
# Filter based on serial number
min_sn = NumberFilter(name='serial', lookup_expr='gte')
max_sn = NumberFilter(name='serial', lookup_expr='lte')
class Meta:
model = UniquePart
fields = ['serial', 'part', 'customer']
class UniquePartList(generics.ListCreateAPIView):
"""
get:
Return a list of all UniqueParts
(with optional query filter)
post:
Create a new UniquePart
"""
queryset = UniquePart.objects.all()
serializer_class = UniquePartSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filter_class = UniquePartFilter
class PartTrackingDetail(generics.RetrieveUpdateDestroyAPIView):
"""
get:
Return a single PartTrackingInfo object
post:
Update a PartTrackingInfo object
delete:
Remove a PartTrackingInfo object
"""
queryset = PartTrackingInfo.objects.all()
serializer_class = PartTrackingInfoSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class PartTrackingFilter(FilterSet):
class Meta:
model = PartTrackingInfo
fields = ['part']
class PartTrackingList(generics.ListCreateAPIView):
"""
get:
Return a list of all PartTrackingInfo objects
(with optional query filter)
post:
Create a new PartTrackingInfo object
"""
queryset = PartTrackingInfo.objects.all()
serializer_class = PartTrackingInfoSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
filter_backends = (DjangoFilterBackend,)
filter_class = PartTrackingFilter

View File

@ -1,7 +0,0 @@
from __future__ import unicode_literals
from django.apps import AppConfig
class TrackConfig(AppConfig):
name = 'track'

View File

@ -1,28 +0,0 @@
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from .models import UniquePart
class EditTrackedPartForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(EditTrackedPartForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'id-edit-part-form'
self.helper.form_class = 'blueForms'
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Submit'))
class Meta:
model = UniquePart
fields = [
'part',
'serial',
'URL',
'customer',
'status'
]

View File

@ -1,47 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-12 05:02
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('part', '0001_initial'),
('supplier', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PartTrackingInfo',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(auto_now_add=True)),
('notes', models.CharField(max_length=500)),
],
),
migrations.CreateModel(
name='UniquePart',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('creation_date', models.DateField(auto_now_add=True)),
('serial', models.IntegerField()),
('status', models.IntegerField(choices=[(0, 'In progress'), (40, 'Damaged'), (10, 'In stock'), (50, 'Destroyed'), (20, 'Shipped'), (30, 'Returned')], default=0)),
('customer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='supplier.Customer')),
('part', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='part.Part')),
],
),
migrations.AddField(
model_name='parttrackinginfo',
name='part',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tracking_info', to='track.UniquePart'),
),
migrations.AlterUniqueTogether(
name='uniquepart',
unique_together=set([('part', 'serial')]),
),
]

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-13 14:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('track', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='uniquepart',
name='part',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='serials', to='part.Part'),
),
migrations.AlterField(
model_name='uniquepart',
name='serial',
field=models.PositiveIntegerField(),
),
]

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-15 01:47
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('track', '0002_auto_20180413_1440'),
]
operations = [
migrations.AddField(
model_name='parttrackinginfo',
name='title',
field=models.CharField(default='tracking information', max_length=250),
preserve_default=False,
),
migrations.AlterField(
model_name='parttrackinginfo',
name='notes',
field=models.CharField(blank=True, max_length=1024),
),
migrations.AlterField(
model_name='uniquepart',
name='status',
field=models.IntegerField(choices=[(0, 'In progress'), (35, 'Repaired'), (40, 'Damaged'), (10, 'In stock'), (50, 'Destroyed'), (20, 'Shipped'), (30, 'Returned')], default=0),
),
]

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-15 01:50
from __future__ import unicode_literals
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),
('track', '0003_auto_20180415_0147'),
]
operations = [
migrations.AddField(
model_name='parttrackinginfo',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2018-04-15 15:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('track', '0004_parttrackinginfo_user'),
]
operations = [
migrations.AddField(
model_name='uniquepart',
name='URL',
field=models.URLField(blank=True),
),
]

View File

@ -1,78 +0,0 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.db import models
from django.contrib.auth.models import User
from supplier.models import Customer
from part.models import Part
class UniquePart(models.Model):
""" A unique instance of a Part object.
Used for tracking parts based on serial numbers,
and tracking all events in the life of a part
"""
def get_absolute_url(self):
return "/track/{id}/".format(id=self.id)
class Meta:
# Cannot have multiple parts with same serial number
unique_together = ('part', 'serial')
part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='serials')
creation_date = models.DateField(auto_now_add=True,
editable=False)
serial = models.PositiveIntegerField()
# Provide a URL for an external link
URL = models.URLField(blank=True)
# createdBy = models.ForeignKey(User)
customer = models.ForeignKey(Customer, blank=True, null=True)
# Part status types
PART_IN_PROGRESS = 0
PART_IN_STOCK = 10
PART_SHIPPED = 20
PART_RETURNED = 30
PART_REPAIRED = 35
PART_DAMAGED = 40
PART_DESTROYED = 50
PART_STATUS_CODES = {
PART_IN_PROGRESS: _("In progress"),
PART_IN_STOCK: _("In stock"),
PART_SHIPPED: _("Shipped"),
PART_RETURNED: _("Returned"),
PART_REPAIRED: _("Repaired"),
PART_DAMAGED: _("Damaged"),
PART_DESTROYED: _("Destroyed")
}
status = models.IntegerField(default=PART_IN_PROGRESS, choices=PART_STATUS_CODES.items())
def __str__(self):
return "{pn} - # {sn}".format(pn=self.part.name,
sn=self.serial)
class PartTrackingInfo(models.Model):
""" Single data-point in the life of a UniquePart
Each time something happens to the UniquePart,
a new PartTrackingInfo object should be created.
"""
part = models.ForeignKey(UniquePart, on_delete=models.CASCADE, related_name='tracking_info')
date = models.DateField(auto_now_add=True, editable=False)
title = models.CharField(max_length=250)
notes = models.CharField(max_length=1024, blank=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)

View File

@ -1,23 +0,0 @@
from rest_framework import serializers
from .models import UniquePart, PartTrackingInfo
class UniquePartSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UniquePart
fields = ['url',
'part',
'creation_date',
'serial',
# 'createdBy',
'customer',
'status']
class PartTrackingInfoSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PartTrackingInfo
fields = '__all__'

View File

@ -1,5 +0,0 @@
{% extends "create_edit_obj.html" %}
{% block obj_title %}
Create a new tracked part
{% endblock %}

View File

@ -1,11 +0,0 @@
{% extends "delete_obj.html" %}
{% block del_title %}
Are you sure you want to delete tracking info for this part?
{% endblock %}
{% block del_body %}
All tracking information for part <b>{{ track.part.name }} SN-{{ track.serial }}</b> will be deleted.
{% endblock %}

View File

@ -1,59 +0,0 @@
{% extends "base.html" %}
{% block content %}
<h3>Part tracking information</h3>
<table class="table table-striped">
<tr>
<td>Part</td>
<td><a href="{% url 'part-track' part.part.id %}">{{ part.part.name }}</a></td>
</tr>
<tr>
<td>Serial Number</td>
<td>{{ part.serial }}</td>
</tr>
<tr>
<td>Creation Date</td>
<td>{{ part.creation_date }}</td>
</tr>
{% if part.URL %}
<tr>
<td>URL</td>
<td><a href="{{ part.URL }}">{{ part.URL }}</a></td>
</tr>
{% endif %}
{% if part.customer %}
<tr>
<td>Customer</td>
<td>{{ part.customer }}</td>
</tr>
{% endif %}
<tr>
<td>Status</td>
<td>{{ part.get_status_display }}</td>
</tr>
</table>
{% if part.tracking_info.all|length > 0 %}
<p>Tracking information:</p>
<ul class='list-group'>
{% for info in part.tracking_info.all %}
<li class='list-group-item'>
{{ info.title }}
{% if info.note %}<br><i>{{ info.notes }}</i>{% endif %}<span class="badge">{{ info.date }}</span>
</li>
{% endfor %}
</ul>
{% endif %}
<div class='container-fluid'>
<a href="{% url 'track-edit' part.id %}">
<button class="btn btn-info">Edit</button>
</a>
<a href="{% url 'track-delete' part.id %}">
<button class="btn btn-danger">Delete</button>
</a>
</div>
{% endblock %}

View File

@ -1,5 +0,0 @@
{% extends "create_edit_obj.html" %}
{% block obj_title %}
Edit tracked part information
{% endblock %}

View File

@ -1,39 +0,0 @@
{% extends "base.html" %}
{% block content %}
<h3>Part Tracking</h3>
<ul class='list-group'>
{% for part in parts.all %}
<li class='list-group-item'>
<a href="{% url 'track-detail' part.id %}">
{{ part.part.name }} - SN {{ part.serial }}
</a>
</li>
{% endfor %}
</ul>
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
{% endif %}
</span>
</div>
{% endif %}
<div class='container-fluid'>
<a href="{% url 'track-create' %}">
<button class="btn btn-success">New Tracked Part</button>
</a>
</div>
{% endblock %}

View File

@ -1,5 +0,0 @@
{% extends "base.html" %}
{% block content %}
{% endblock %}

View File

@ -1,3 +0,0 @@
# from django.test import TestCase
# Create your tests here.

View File

@ -1,41 +0,0 @@
from django.conf.urls import url, include
from . import views
"""
TODO - Implement JSON API for part serial number tracking
part_track_api_urls = [
url(r'^(?P<pk>[0-9]+)/?$', api.PartTrackingDetail.as_view(), name='parttrackinginfo-detail'),
url(r'^\?.*/?$', api.PartTrackingList.as_view()),
url(r'^$', api.PartTrackingList.as_view())
]
unique_api_urls = [
# Detail for a single unique part
url(r'^(?P<pk>[0-9]+)/?$', api.UniquePartDetail.as_view(), name='uniquepart-detail'),
# List all unique parts, with optional filters
url(r'^\?.*/?$', api.UniquePartList.as_view()),
url(r'^$', api.UniquePartList.as_view()),
]
"""
track_detail_urls = [
url(r'^edit/?', views.TrackEdit.as_view(), name='track-edit'),
url(r'^delete/?', views.TrackDelete.as_view(), name='track-delete'),
url('^.*$', views.TrackDetail.as_view(), name='track-detail'),
]
tracking_urls = [
# Detail view
url(r'^(?P<pk>\d+)/', include(track_detail_urls)),
# Create a new tracking item
url(r'^new/?', views.TrackCreate.as_view(), name='track-create'),
# List ALL tracked items
url(r'^.*$', views.TrackIndex.as_view(), name='track-index'),
]

View File

@ -1,63 +0,0 @@
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.views.generic import DetailView, ListView
from django.views.generic.edit import UpdateView, DeleteView, CreateView
from part.models import Part
from .models import UniquePart
from .forms import EditTrackedPartForm
class TrackIndex(ListView):
model = UniquePart
template_name = 'track/index.html'
context_object_name = 'parts'
paginate_by = 50
def get_queryset(self):
return UniquePart.objects.order_by('part__name', 'serial')
class TrackDetail(DetailView):
queryset = UniquePart.objects.all()
template_name = 'track/detail.html'
context_object_name = 'part'
class TrackCreate(CreateView):
model = UniquePart
form_class = EditTrackedPartForm
template_name = 'track/create.html'
context_object_name = 'part'
def get_initial(self):
initials = super(TrackCreate, self).get_initial().copy()
part_id = self.request.GET.get('part', None)
if part_id:
initials['part'] = get_object_or_404(Part, pk=part_id)
return initials
class TrackEdit(UpdateView):
model = UniquePart
form_class = EditTrackedPartForm
template_name = 'track/edit.html'
context_obect_name = 'part'
class TrackDelete(DeleteView):
model = UniquePart
success_url = '/track'
template_name = 'track/delete.html'
context_object_name = 'track'
def post(self, request, *args, **kwargs):
if 'confirm' in request.POST:
return super(TrackDelete, self).post(request, *args, **kwargs)
else:
return HttpResponseRedirect(self.get_object().get_absolute_url())

View File

@ -18,7 +18,6 @@ migrate:
python InvenTree/manage.py makemigrations part
python InvenTree/manage.py makemigrations stock
python InvenTree/manage.py makemigrations supplier
python InvenTree/manage.py makemigrations track
python InvenTree/manage.py migrate --run-syncdb
python InvenTree/manage.py check