diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index f50dc0aef8..500b4a2bdf 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -127,6 +127,8 @@ class PartList(generics.ListCreateAPIView): ] filter_fields = [ + 'is_template', + 'variant_of', 'buildable', 'consumable', 'trackable', diff --git a/InvenTree/part/forms.py b/InvenTree/part/forms.py index cbc1cdaf8c..a2ec613429 100644 --- a/InvenTree/part/forms.py +++ b/InvenTree/part/forms.py @@ -92,7 +92,6 @@ class EditPartForm(HelperForm): 'category', 'name', 'IPN', - 'variant', 'is_template', 'variant_of', 'description', diff --git a/InvenTree/part/migrations/0005_auto_20190526_1119.py b/InvenTree/part/migrations/0005_auto_20190526_1119.py new file mode 100644 index 0000000000..36d55188f0 --- /dev/null +++ b/InvenTree/part/migrations/0005_auto_20190526_1119.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2 on 2019-05-26 01:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0004_auto_20190525_2356'), + ] + + operations = [ + migrations.AddField( + model_name='part', + name='revision', + field=models.CharField(blank=True, help_text='Part rerevision code', max_length=32), + ), + migrations.AlterUniqueTogether( + name='part', + unique_together={('name', 'revision')}, + ), + migrations.RemoveField( + model_name='part', + name='variant', + ), + ] diff --git a/InvenTree/part/migrations/0006_auto_20190526_1215.py b/InvenTree/part/migrations/0006_auto_20190526_1215.py new file mode 100644 index 0000000000..b91cc8bbab --- /dev/null +++ b/InvenTree/part/migrations/0006_auto_20190526_1215.py @@ -0,0 +1,27 @@ +# Generated by Django 2.2 on 2019-05-26 02:15 + +import InvenTree.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('part', '0005_auto_20190526_1119'), + ] + + operations = [ + migrations.AlterField( + model_name='part', + name='name', + field=models.CharField(help_text='Part name (must be unique)', max_length=100, unique=True, validators=[InvenTree.validators.validate_part_name]), + ), + migrations.AlterUniqueTogether( + name='part', + unique_together=set(), + ), + migrations.RemoveField( + model_name='part', + name='revision', + ), + ] diff --git a/InvenTree/part/models.py b/InvenTree/part/models.py index 3b9a7aea60..048330c2c6 100644 --- a/InvenTree/part/models.py +++ b/InvenTree/part/models.py @@ -162,6 +162,7 @@ def match_part_names(match, threshold=80, reverse=True, compare_length=False): if compare_length: # Also employ primitive length comparison + # TODO - Improve this somewhat... l_min = min(len(match), len(compare)) l_max = max(len(match), len(compare)) @@ -211,9 +212,6 @@ class Part(models.Model): class Meta: verbose_name = "Part" verbose_name_plural = "Parts" - unique_together = [ - ('name', 'variant') - ] def __str__(self): return "{n} - {d}".format(n=self.full_name, d=self.description) @@ -236,9 +234,6 @@ class Part(models.Model): elements.append(self.name) - if self.variant: - elements.append(self.variant) - return ' | '.join(elements) def get_absolute_url(self): @@ -262,12 +257,11 @@ class Part(models.Model): 'variant_of': _("Part cannot be a variant of another part if it is already a template"), }) - name = models.CharField(max_length=100, blank=False, help_text='Part name', + name = models.CharField(max_length=100, blank=False, unique=True, + help_text='Part name (must be unique)', validators=[validators.validate_part_name] ) - variant = models.CharField(max_length=32, blank=True, help_text='Part variant or revision code') - is_template = models.BooleanField(default=False, help_text='Is this part a template part?') variant_of = models.ForeignKey('part.Part', related_name='variants', diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 540f754765..fe858091c7 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -87,7 +87,6 @@ class PartSerializer(serializers.ModelSerializer): 'IPN', 'is_template', 'variant_of', - 'variant', 'description', 'keywords', 'URL', diff --git a/InvenTree/part/templates/part/detail.html b/InvenTree/part/templates/part/detail.html index 068731da64..9765a2574e 100644 --- a/InvenTree/part/templates/part/detail.html +++ b/InvenTree/part/templates/part/detail.html @@ -42,12 +42,6 @@ {{ part.IPN }} {% endif %} - {% if part.variant %} - - Variant - {{ part.variant }} - - {% endif %} Description {{ part.description }} diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index f91c5ac1e2..d8af6d2ec9 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -38,9 +38,6 @@

{{ part.full_name }}

- {% if part.variant %} -

Variant: {{ part.variant }}

- {% endif %}

{{ part.description }}

diff --git a/InvenTree/part/templates/part/variant_part.html b/InvenTree/part/templates/part/variant_part.html new file mode 100644 index 0000000000..eac8762947 --- /dev/null +++ b/InvenTree/part/templates/part/variant_part.html @@ -0,0 +1,12 @@ +{% extends "modal_form.html" %} + +{% block pre_form_content %} + +{{ block.super }} + +
+ Create new part variant
+ Create a new variant of template '{{ part.full_name }}'. +
+ +{% endblock %} \ No newline at end of file diff --git a/InvenTree/part/templates/part/variants.html b/InvenTree/part/templates/part/variants.html index 0cf9735364..98ac08a8d3 100644 --- a/InvenTree/part/templates/part/variants.html +++ b/InvenTree/part/templates/part/variants.html @@ -60,4 +60,13 @@ sortable: true, }); + $('#new-variant').click(function() { + launchModalForm( + "{% url 'make-part-variant' part.id %}", + { + follow: true, + } + ); + }); + {% endblock %} \ No newline at end of file diff --git a/InvenTree/part/urls.py b/InvenTree/part/urls.py index c8582160bb..0147cd4d07 100644 --- a/InvenTree/part/urls.py +++ b/InvenTree/part/urls.py @@ -24,6 +24,7 @@ part_detail_urls = [ url(r'^bom-export/?', views.BomDownload.as_view(), name='bom-export'), url(r'^validate-bom/', views.BomValidate.as_view(), name='bom-validate'), url(r'^duplicate/', views.PartDuplicate.as_view(), name='part-duplicate'), + url(r'^make-variant/', views.MakePartVariant.as_view(), name='make-part-variant'), url(r'^pricing/', views.PartPricing.as_view(), name='part-pricing'), url(r'^variants/?', views.PartDetail.as_view(template_name='part/variants.html'), name='part-variants'), diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index 02b5b19909..4f61036f95 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -124,6 +124,77 @@ class PartAttachmentDelete(AjaxDeleteView): } +class MakePartVariant(AjaxCreateView): + """ View for creating a new variant based on an existing template Part + + - Part is provided in the URL '/part//make_variant/' + - Automatically copy relevent data (BOM, etc, etc) + + """ + + model = Part + form_class = part_forms.EditPartForm + + ajax_form_title = 'Create Variant' + ajax_template_name = 'part/variant_part.html' + + def get_part_template(self): + return get_object_or_404(Part, id=self.kwargs['pk']) + + def get_context_data(self): + return { + 'part': self.get_part_template(), + } + + def get_form(self): + form = super(AjaxCreateView, self).get_form() + + # Hide some variant-related fields + form.fields['is_template'].widget = HiddenInput() + form.fields['variant_of'].widget = HiddenInput() + + return form + + def post(self, request, *args, **kwargs): + + form = self.get_form() + context = self.get_context_data() + part_template = self.get_part_template() + + valid = form.is_valid() + + data = { + 'form_valid': valid, + } + + if valid: + # Create the new part variant + part = form.save(commit=False) + part.variant_of = part_template + part.is_template = False + + part.save() + + data['pk'] = part.pk + data['text'] = str(part) + data['url'] = part.get_absolute_url() + + # Copy relevent information from the template part + part.deepCopy(part_template, bom=True) + + return self.renderJsonResponse(request, form, data, context=context) + + def get_initial(self): + + part_template = self.get_part_template() + + initials = model_to_dict(part_template) + initials['is_template'] = False + initials['variant_of'] = part_template + + return initials + + class PartDuplicate(AjaxCreateView): """ View for duplicating an existing Part object. @@ -209,7 +280,7 @@ class PartDuplicate(AjaxCreateView): original = self.get_part_to_copy() if original: - part.deepCopy(original, bom=deep_copy) + part.deepCopy(original, bom=deep_copy) try: data['url'] = part.get_absolute_url()