nicorellius
nicorellius

Reputation: 4043

Custom Django SlugField Validation Breaks URL Matching

I wrote a custom field validator for slugs in my URLs (accepting periods in slugs for one model, SLUG_REGEX = '[-a-zA-Z0-9_.]+$'):

def validate_my_slug(value):

    my_slug_re = re.compile(settings.SLUG_REGEX)

    if not my_slug_re.match(value):
        raise ValidationError(
            _('Slugs must consist of letters, numbers, '
              'underscores, hyphens, or periods.'),
            params={'value': value},
        )

The field in models.py:

slug = models.CharField(max_length=64, null=True, blank=True, 
                        validators=[validate_my_slug])

This works when saving models in Django admin. But then in my URLConf, this no longer works (it did work before changing the slug validation):

path('product-release/<slug:slug>', ProductReleaseDetail.as_view(), 
     name='product-detail')
  1. I found a solution using re_path, but I'm not sure it's the right/best way to do it:

     re_path('product-release/(?P<slug>[-a-zA-Z0-9_.]+)$', 
             ProductReleaseDetail.as_view(), 
             name='product-release-detail')
    
  2. Now this no longer works (comes before the re_path pattern above in urls.py):

     path('product-releases', ProductReleaseList.as_view(), 
          name='product-release-list')
    

Everything works except those matches to product release list. When I changed the slug validator, the URL http://localhost:8080/product-releases stopped working, with the error:

NoReverseMatch at /product-releases
Reverse for 'product-release-detail' with no arguments not found.
1 pattern(s) tried: ['product-release/(?P<slug>[-a-zA-Z0-9_.]+$)']

But this seems wrong since I'm not even trying to reach product-release-detail; I'm trying to reach product-release-list. I must be missing something simple here, but now my vision is clouded.

views.py

. . .

class ProductReleaseList(ListView):
    model = ProductRelease


class ProductReleaseDetail(DetailView):
    model = ProductRelease

. . .

urlpatterns

. . .

# Product Releases
path('product-releases', ProductReleaseList.as_view(), name='product-release-list'),
re_path('product-release/(?P<slug>[-a-zA-Z0-9_.]+$)', ProductReleaseDetail.as_view(), name='product-release-detail'),
# Release Notes
path('release-notes', ReleaseNoteList.as_view(), name='release-note-list'),
re_path('release-note/(?P<slug>[-a-zA-Z0-9_.]+$)', ReleaseNoteDetail.as_view(), name='release-note-detail'),

. . .

productrelease_list.html

. . .

<h4><a href="{% url 'product-release-detail' %}">{{ p }}</a></h4>

. . .

Upvotes: 0

Views: 498

Answers (1)

user1600649
user1600649

Reputation:

In your template you're using something akin to:

{% url "product-release-detail" varname %}

This variable however can apparently be empty (or is even always empty due to a spelling mistake). With /<slug:slug> this goes through (which I don't agree with btw, but that's besides the point). With your more restrictive and better re_path, this now generates an error (correctly), because /product-release/ is not a valid url: a slug should at least have one character.

So the root cause is ether, that the variable is incorrectly named or that you have products without slugs (which you shouldn't have).

Address comments

  1. Commenting out in templates is done with {# #}. Make sure you didn't use <!-- -->, because that still executes the code.
  2. As it stands {% url "product-release-detail" %} is invalid: it requires an argument, namely a slug. If you want to link to the list view, use {% url "product-release-list" %} as that can be called without argument.
  3. I don't know what {{ p }} is, but I'm assuming the product object. In that case, you should use {%url "product-release-detail" p.name_of_slug_field %}.

Upvotes: 1

Related Questions