1

In my Django apps, I have many urls including /(?P<project_name>[_\w]+)/. The project_name is defined by users and it is an attribute of the Project model.

I've added a validator on the project_name to check if it's lowercase. So new names are all lowercase but some old names include uppercase characters.

I would like to change all the names stored to make them lowercase but at the same time, I don't want users to get an error when trying to access to one of the url with the old project name including uppercase characters. As I have many urls and many views, I don't want to update each to manually .lower() the project_name.

Is there a way to redirect all urls including /<project_NAME>/ to /<project_name>/?

Melissa
  • 55
  • 7
  • Why do you make `project_name`s with uppercase in the first case? Normally one uses a [*slug*](https://docs.djangoproject.com/en/3.0/glossary/#term-slug) for that. – Willem Van Onsem May 05 '20 at 10:43
  • Well, it was a mistake... that I now want to correct. – Melissa May 05 '20 at 10:54
  • I think it will probably be easier to just add `SlugField`s to your models. Especially since `DetailView`s and `UpdateView`s have support for slugs (they filter automatically on it). Furthermore a slug does not only is put in lowercase, it removes diacritics, and other characters that introduce percent encoding in URLs, so it will make urls more "clean". – Willem Van Onsem May 05 '20 at 10:59
  • Write a custom middleware to check the URL and do the redirection. – Kevin Christopher Henry May 05 '20 at 11:03
  • @KevinChristopherHenry Could you please detail a little bit more how this could be done using a middleware? – Melissa May 05 '20 at 11:30

2 Answers2

1

Hacky way with decorators

You could create a decorator for all your views that use a project_name:

def project_lowercase(_func=None):
    def checkLowercase(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            request = args[0]
            project_name = args[1]
            if not project_name.islower():
                return HttpResponseRedirect(reverse(your_url_name, kwargs{'project_name' = project_name.lower()))
            return func(*args, **kwargs)
        return wrapper
    return checkLowercase(_func)

Replace your_url_name with whatever you named your url route, then import the function and add the decorator above each view function:

@project_lowercase
def view_project(request, project_name):
    # ...
    return "Hello World!"

Slugs would be better

Alternitively as suggested in the comments and a better solution you should use a slug to store urls for your projects, have a look here to see how to add them to your models and generate slugs.

To move your existing data to use slugs or just to update project names you can create a data migration that will alter existing data in your database.

GTBebbo
  • 1,198
  • 1
  • 8
  • 17
  • As I have many views, I wanted to avoid changing each of them. But using a decorator as you suggest is adding a minimal change to each so it's not so annoying! – Melissa May 05 '20 at 11:18
0

The first option I would suggest is to avoid regular expressions, switch to path for your urlpatterns and use custom url converter for your project names.

In case you want to keep using regular expressions, you could restrict your pattern to only accept lower case project names /(?P<project_name>[_a-z0-9]+)/. After that add url pattern, which is (effectively) the same as your current one /(?P<project_name>\w+)/ (note missing _ - \w already includes that) to match all project names, including the legacy ones, the view for that pattern would redirect to your first view with the lower case project_name.

zaquest
  • 2,030
  • 17
  • 25
  • `path` seems indeed a better solution ! The second option is the one I thought about but I want to avoid duplicating all the urls. – Melissa May 05 '20 at 11:23