mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Merge pull request #962 from eeintech/part_ipn_slug
Added PartDetailFromIPN view (subclass of PartDetail) and url pattern
This commit is contained in:
		@@ -6,7 +6,7 @@ from InvenTree.settings import *
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Override the 'test' database
 | 
					# Override the 'test' database
 | 
				
			||||||
if 'test' in sys.argv:
 | 
					if 'test' in sys.argv:
 | 
				
			||||||
    eprint('InvenTree: Running tests - Using MySQL test database')
 | 
					    eprint('InvenTree: Running tests - Using PostGreSQL test database')
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    DATABASES['default'] = {
 | 
					    DATABASES['default'] = {
 | 
				
			||||||
        # Ensure postgresql backend is being used
 | 
					        # Ensure postgresql backend is being used
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -78,6 +78,56 @@ class PartDetailTest(PartViewTestCase):
 | 
				
			|||||||
        self.assertEqual(response.status_code, 200)
 | 
					        self.assertEqual(response.status_code, 200)
 | 
				
			||||||
        self.assertTrue(response.context['editing_enabled'])
 | 
					        self.assertTrue(response.context['editing_enabled'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_part_detail_from_ipn(self):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Test that we can retrieve a part detail page from part IPN:
 | 
				
			||||||
 | 
					        - if no part with matching IPN -> return part index
 | 
				
			||||||
 | 
					        - if unique IPN match -> return part detail page
 | 
				
			||||||
 | 
					        - if multiple IPN matches -> return part index
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        ipn_test = 'PART-000000-AA'
 | 
				
			||||||
 | 
					        pk = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def test_ipn_match(index_result=False, detail_result=False):
 | 
				
			||||||
 | 
					            index_redirect = False
 | 
				
			||||||
 | 
					            detail_redirect = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            response = self.client.get(reverse('part-detail-from-ipn', args=(ipn_test,)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Check for PartIndex redirect
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if response.url == '/part/':
 | 
				
			||||||
 | 
					                    index_redirect = True
 | 
				
			||||||
 | 
					            except AttributeError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Check for PartDetail redirect
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if response.context['part'].pk == pk:
 | 
				
			||||||
 | 
					                    detail_redirect = True
 | 
				
			||||||
 | 
					            except TypeError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.assertEqual(index_result, index_redirect)
 | 
				
			||||||
 | 
					            self.assertEqual(detail_result, detail_redirect)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test no match
 | 
				
			||||||
 | 
					        test_ipn_match(index_result=True, detail_result=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test unique match
 | 
				
			||||||
 | 
					        part = Part.objects.get(pk=pk)
 | 
				
			||||||
 | 
					        part.IPN = ipn_test
 | 
				
			||||||
 | 
					        part.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_ipn_match(index_result=False, detail_result=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Test multiple matches
 | 
				
			||||||
 | 
					        part = Part.objects.get(pk=pk + 1)
 | 
				
			||||||
 | 
					        part.IPN = ipn_test
 | 
				
			||||||
 | 
					        part.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        test_ipn_match(index_result=True, detail_result=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_bom_download(self):
 | 
					    def test_bom_download(self):
 | 
				
			||||||
        """ Test downloading a BOM for a valid part """
 | 
					        """ Test downloading a BOM for a valid part """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -99,7 +99,7 @@ part_urls = [
 | 
				
			|||||||
    # Export data for multiple parts
 | 
					    # Export data for multiple parts
 | 
				
			||||||
    url(r'^export/', views.PartExport.as_view(), name='part-export'),
 | 
					    url(r'^export/', views.PartExport.as_view(), name='part-export'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Individual part
 | 
					    # Individual part using pk
 | 
				
			||||||
    url(r'^(?P<pk>\d+)/', include(part_detail_urls)),
 | 
					    url(r'^(?P<pk>\d+)/', include(part_detail_urls)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Part category
 | 
					    # Part category
 | 
				
			||||||
@@ -124,6 +124,9 @@ part_urls = [
 | 
				
			|||||||
    # Bom Items
 | 
					    # Bom Items
 | 
				
			||||||
    url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
 | 
					    url(r'^bom/(?P<pk>\d+)/', include(part_bom_urls)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Individual part using IPN as slug
 | 
				
			||||||
 | 
					    url(r'^(?P<slug>[-\w]+)/', views.PartDetailFromIPN.as_view(), name='part-detail-from-ipn'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Top level part list (display top level parts and categories)
 | 
					    # Top level part list (display top level parts and categories)
 | 
				
			||||||
    url(r'^.*$', views.PartIndex.as_view(), name='part-index'),
 | 
					    url(r'^.*$', views.PartIndex.as_view(), name='part-index'),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -663,6 +663,43 @@ class PartDetail(DetailView):
 | 
				
			|||||||
        return context
 | 
					        return context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PartDetailFromIPN(PartDetail):
 | 
				
			||||||
 | 
					    slug_field = 'IPN'
 | 
				
			||||||
 | 
					    slug_url_kwarg = 'slug'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_object(self):
 | 
				
			||||||
 | 
					        """ Return Part object which IPN field matches the slug value """
 | 
				
			||||||
 | 
					        queryset = self.get_queryset()
 | 
				
			||||||
 | 
					        # Get slug
 | 
				
			||||||
 | 
					        slug = self.kwargs.get(self.slug_url_kwarg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if slug is not None:
 | 
				
			||||||
 | 
					            slug_field = self.get_slug_field()
 | 
				
			||||||
 | 
					            # Filter by the slug value
 | 
				
			||||||
 | 
					            queryset = queryset.filter(**{slug_field: slug})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                # Get unique part from queryset
 | 
				
			||||||
 | 
					                part = queryset.get()
 | 
				
			||||||
 | 
					                # Return Part object
 | 
				
			||||||
 | 
					                return part
 | 
				
			||||||
 | 
					            except queryset.model.MultipleObjectsReturned:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					            except queryset.model.DoesNotExist:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get(self, request, *args, **kwargs):
 | 
				
			||||||
 | 
					        """ Attempt to match slug to a Part, else redirect to PartIndex view """
 | 
				
			||||||
 | 
					        self.object = self.get_object()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not self.object:
 | 
				
			||||||
 | 
					            return HttpResponseRedirect(reverse('part-index'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return super(PartDetailFromIPN, self).get(request, *args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PartQRCode(QRCodeView):
 | 
					class PartQRCode(QRCodeView):
 | 
				
			||||||
    """ View for displaying a QR code for a Part object """
 | 
					    """ View for displaying a QR code for a Part object """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user