Custom url for login/logout

I’m using login/logout functionality built into Django but have 2 issues:

  1. I can’t seem to figure out how to change the urls form /accounts/login or logout to appname/login or logout. The templates are in my project directory in templates/registration.

  2. My HTML doesn’t display {{ message }} text nor can I center on teh page built in messages such as when an incorrect pw/userid is entered. Templates is commented out because it gives an error:

django.core.exceptions.ImproperlyConfigured: Invalid BACKEND for a template engine: <not defined>. Check your TEMPLATES setting.
'''TEMPLATES = [
    {
        "DIRS": [BASE_DIR / "templates"],  # new


    },
]'''


LOGIN_URL = "login"
LOGOUT_URL = "login"
LOGOUT_REDIRECT_URL = "login"
LOGIN_REDIRECT_URL = "/ISO22301/welcome/"

HTML

<!DOCTYPE html>
<html lang = "en">


  <head>
    {% load static %}
    <link rel="stylesheet" href="{% static 'static/css/styles.css' %}">
    <title>{{ browsertab }}</title>
    <link rel='shortcut icon' type="image/png" href="{% static 'static/images/favicon.png' %}" >



    <h1>{{ dashboard_title }}</h1>

  </head>
<meta name="viewport" content="width=device-width, initial-scale=1">

<body>
<h1>Welcome to Strategy Mapping</h1>
<h1>Login</h1>

{% for message in messages %}
<center>
          <div class='loginmessage'> {{ message }}</div>
          <p></p>
{% endfor %}
</center>
<form method='POST' action='{% url 'login' %}' enctype='multipart/form-data'>
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Log In</button>
</form>

</body>

urls


urlpatterns = [
    #path('login/', views.user_login, name='login'),
    path('home/', views.home, name='home'),
    path('welcome/', views.welcome, name='welcome'),
    path('dashboard/', views.dashboard, name='dashboard'),
    path('testpage/', views.testpage, name='testpage'),
    #path('logout/', views.user_logout, name='logout_user'),

]

views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('django.contrib.auth.urls')),
    path("ISO22301/", include("ISO22301.urls")),
]

def user_login(request):
    form = LoginForm()
    context ={
        'browsertab': 'Strategia Worldwide',
        'form': form,
        }
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            user = authenticate(request, username=username, password=password)
            if user is not None:
                login(request, user)   
                current_time = datetime.datetime.now()
                return redirect('welcome')
        return render(request,'login.html',context)

    else:
        form = LoginForm()
        context ={
             'browsertab': 'Strategia Worldwide',
             'form': form,
        }
    return render(request,'login.html',context)


def user_logout(request):
    if request.method == 'POST':
        if request.user.is_authenticated:
            logout(request) 
            messages.success(request, 'Logout Successful')
    return redirect('/accounts/login.html')

You’re showing two urlpatterns here. What files (and directories) are these in?

Note: If you’ve a URL defined with the name login and you are using the django.contrib.auth.urls, then you’ve got two urls with the same name. In this case, the order of the definition is important - the url being resolved would be the last one. (But, it’s better if there aren’t any conflicts.)

Regarding the TEMPLATES setting, that’s not a complete definition. If you want to use the "DIRS" attribute, you need to add that to the TEMPLATES setting.

Can you share the contents of your project/urls.py (not the app/urls.py)? In any case, you can change the url to something/login by changing “accounts” to “something” in the relevant project/urls.py (i.e. assuming the entry below is what you have there:)

then change:
path('accounts/', include('django.contrib.auth.urls')),

to:
path('something/', include('django.contrib.auth.urls')),

Django will still use its native Login/Logout modules nonetheless, but the url will have the desired look. If you seriously want “something” to be a custom app – not from Django – that provides login/logout views, then consider adding the relevant views into the named app (i.e. “something”). You do this by subclassing the native Django Login/Logout Views into something/views.py. The urls.py file entries should then look like below:

in projects/urls.py
path('something/', include('something.urls')),

in something/urls.py

from .views import Login, Logout
urlpatterns = [
    path('login/', Login.as_view, name="login"),
]

Note that the native Login/Logout Views in Django may still expect the template files at templates/accounts/. If you want the templates to reside in the templates/something/ folder, you definitely need to subclass the views, as suggested above, to override the default template location.

I hope this made sense

I deleted the paths to login and logout in the ISO22301 app urls.py. and made changes in the project url.py, per onyeibo’s suggestion:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('ISO22301/', include('django.contrib.auth.urls')),
    path("ISO22301/", include("ISO22301.urls")),
]

Changing the path made sense because, if I understand it correctly, Django now uses ISO22301, instead of the default accounts url as the path to the urls used by ‘django.contrib.auth’ just as it used the urls in the urls.py of the ISO22301 app from ‘path(“ISO22301/”, include(“ISO22301.urls”))’

I need to dig more into using Templates in settings. I’ve tried a few combos I found in various tutorials to avail, but I was just throwing jelly at the wall to see what sticks.

That fixed it. I changed it to my app name and it works as desired, except it still does not display any messages I pass to the template. logout(request) renders the login template, since it is the only one in registration/templates directory (I have no logout template).

It appears, from playing around with the code, Django uses my template but not the two user_login and user_logout views, but its own and thus no message is displayed; along with default browser tab and favicons and not the ones I’ve defined.

Thanks to both of you.

Digging a bit deaper, this code is what redirects login to welcome and logout to login:

LOGIN_URL = "login"
LOGOUT_REDIRECT_URL = "login"
LOGIN_REDIRECT_URL = "/ISO22301/welcome/"

correct?

You are understanding it correctly, but it’s still not a great idea to have the same paths twice in the urlpatterns. Doing so leads to potential confusion - and with the way you have it defined, you’re going to use the auth views, not your views, for any urls that are the same in both.

The last two lines, yes.

LOGIN_URL has a different function.

So, if I would put:

path('something/', include('something.urls')),

after admin and before ISO22301, if I had a url in a something app in the project called home and also in ISO22301, it’s going to always use the one in ‘something/’ due to the order I have them in url patterns?

All I am trying to do is have the user use the url ISO22301 for login/logout and use Django’s built-in authentication. I guess as long as I do not duplicate url paths I am OK, but is there a better way?

I can get a working login/logout and customize the template, in my ISO22301 template dir, like I want using 2 views. Is there a reason not to use that approach instead of just using django.contrib.auth?

I am looking at using LoginView as another option.

def user_login(request):
    form = LoginForm()
    context ={
        'browsertab': 'Client',
        'form': form,
        }
    if request.method == 'POST':
        form = LoginForm(request.POST)
        if form.is_valid():
            username = form.cleaned_data['username']
            password = form.cleaned_data['password']
            user = authenticate(request, username=username, password=password)
            if user is not None:
                login(request, user)   
                current_time = datetime.datetime.now()
  
                return redirect('welcome')
        messages.error(request, 'Invalid Username or Password')
        return render(request,'ISO22301/login.html',context)

    else:
        form = LoginForm()
        
        context ={
             'browsertab': 'Client',
             'form': form,
        }
    return render(request,'ISO22301/login.html',context)


def user_logout(request):
    form = LoginForm()
    context ={
        'browsertab': 'Client',
        'form': form,
        }
    if request.method == 'POST':
        if request.user.is_authenticated:
            messages.success(request, 'Logout Successful')
            logout(request) 
    return render(request,'ISO22301/login.html',context)

The name for a url, and the path of a url are two different things.

Briefly, the name of a url is an identifier used in your code to get the path for a url, using features like the reverse function or the url tag. The path of a url is used by Django to find the view to be called when a request is made.
(This is covered in a lot more detail in the Django docs.)

If you have two url patterns that work out to be the same, it’s the first view that is going to be called.

If you have:

and, in ISO22301.url you have something like:

Then this is going to create two entries in the url definitions, both with the path ISO22301/login/. The first is going to refer to django.contrib.auth.views.LoginView, while the second is going to refer to ISO22301.my_views.user_login.

When a request is submitted for the url ISO22301/login/, it’s the first entry that will be match, causing the request to be routed to django.contrib.auth.views.LoginView.

The name of a url works in the opposite direction. If you have two different urls with the same name, when you reference that url by name, then it’s the last url defined by that name that will be returned.

Yes, it’s confusing.

You can avoid this confusion in a couple of different ways.

You can use namespacing for your urls. That helps prevent conflict between the url names.

You can use different root names for your paths - don’t do things like path('ISO22301/'...) twice in your urlpatterns.

Regarding your other questions, I’m not sure I’m following why you want to replace the existing authentication views. Yes, you can rebase the Django authentication views by using something other than accounts/ as the url base for those views.

You don’t even need to include the django.contrib.auth.urls - you can completely define them yourself within the urlpatterns. (Again, the details are all covered in the docs.)

Thanks, it is confusing but also makes sense.

I’m really just trying to do relatively straightforward things:

  1. Having the login url used by a client use the format appname/login, and where other pages are also use the appname/page syntax.
  2. Be able to format existing messages in the template so they are centered, have a colored background, etc.
  3. Render messages such as “You have logged out” while redirecting to the login page.

I’d prefer to use django’s exiting views but can’t seem to get them to do what I want. LoginView seems to be the way but I haven’t figured out how to get it to work properly.

As a result, i went with my own views, my concern is if they are as secure as the built in authentication system. From what I can gather they are just different ways of using the same functionality and thus more a matter of choice than one being ‘better.’

Regarding (2), formatting the messages, you should start by ensuring your HTML is valid. You have several issues there and you can never tell whether they will affect the rendering, and whether different browsers will handle these errors differently:

  1. You have an h1 tag within the page’s head. It should be within the body.
  2. You have a meta tag between head and body. It should be within the head.
  3. You have three h1 tags (including the one currently in the head). This isn’t quite invalid but it’s not ideal - headings should follow a hierarchy of sorts - an h1 for the main heading, h2s for subheadings within that, h3s for any sub-subheading within those, etc.
  4. You’re using the center tag which is deprecated and may not be supported by some browsers. Use CSS to center something. (You’ve also got the opening center tag within the for loop but the closing one outside it, which could cause further problems)
  5. You have <p></p> with nothing inside it.
  6. There’s no closing </html> tag.

I haven’t read it but I’ve seen a lot of people recently mention this new website. HTML for People. It might be worth using that or something similar to get to grips with the basics of HTML, as that will ease a lot of problems with layout etc.

Take some time to study the sample solution below. It might take a while to fully understand how everything pieces together but you’ll get it eventually. I think is a faster way to aggregate all we have been saying above. The project name is “project” and below is the complete tree. The sample spots the same app called “ISO22301” to ease your study.

project tree (folders and files):

.
├── db.sqlite3
├── ISO22301
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── templates
│   │   └── ISO22301
│   │       ├── bye.html
│   │       ├── login.html
│   │       └── welcome.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── manage.py
├── static
│   └── css
│       └── styles.css
├── templates
└── project
    ├── asgi.py
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

Here are the critical entries in project/settings.py:

INSTALLED_APPS = [
    ...
    "ISO22301",
]

LOGOUT_REDIRECT_URL = "ISO22301:bye"
LOGIN_REDIRECT_URL = "ISO22301:welcome"
STATICFILES_DIRS = [
    BASE_DIR / "static",
]

Did you see those URL strings with a colon delimiter? The first part tells the URL resolver the app to search, the second part is the url name. You need to define STATICFILES_DIRS so that Django can find static files that are not inside the apps. The above tells Django to start from the Project root, then into the static folder etc. (i.e. /static/css/styles.css)

project/urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path('ISO22301/', include('ISO22301.urls')),
]

Notice that for every url that starts with ISO22301 Django will search /ISO22301/urls.py to resolve the remaining parts

/ISO22301/urls.py:

from django.urls import path
from .views import Login, Logout, bye, welcome

app_name = "ISO22301"

urlpatterns = [
    path("bye/", bye, name="bye"),
    path("login/", Login.as_view(), name="login"),
    path("logout/", Logout.as_view(), name="logout"),
    path("welcome/", welcome, name="welcome"),
]

/ISO22301/views.py:

from django.shortcuts import render
from django.contrib.auth.views import LoginView, LogoutView

# Create your views here.

class Login(LoginView):
    """Your own version Login View.
    This inherits all functionality of Django's native Login View
    But uses your template"""

    template_name = "ISO22301/login.html"
    extra_context = {'browsertab': 'Strategia Worldwide',}


class Logout(LogoutView):
    """Your own version Logout View.
    This inherits all functionality of Django's native Logout View
    But uses your template"""
    #template_name = "ISO22301/logout.html"


def bye(request):
    """log out redirection page"""

    context = {}
    return render(request, 'ISO22301/bye.html', context)


def welcome(request):
    """welcome page"""

    context = {}
    return render(request, 'ISO22301/welcome.html', context)

In the above views, You subclass the native login/logout views (LoginView and LogoutView) to create ours. You need a fair knowledge of class objects in Python to understand what is happening there. The short version is that you are building on top of the native views. Your versions (Login and Logout) inherit all the attributes of the native views. You then added the varied or unique attributes.

Now look at the templates that the above views are rendering. Take note of the file path (ISO22301/templates/ISO22301/). By default, Django will search the templates folder in an app, but the files will go into a subfolder bearing the name of the app instead (e.g. project/app/templates/app/)

templates/ISO22301/login.html

<!DOCTYPE html>
{% load static %}
<html lang = "en">
    <head>
        <link rel="stylesheet" href="{% static 'css/styles.css' %}">
        <title>{{ browsertab }}</title>
        <link rel='shortcut icon' type="image/png" href="{% static 'images/favicon.png' %}" >
    </head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <body>
        <h1>Welcome to Strategy Mapping</h1>
        <h1>Login</h1>
        <form method='POST' action="{% url 'ISO22301:login' %}" enctype='multipart/form-data'>
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit">Log In</button>
        </form>
    </body>
</html>

templates/ISO22301/welcome.html

{% load static %}
<html>
    <h1>Welcome</h1>
    <form class="logout" action="{% url 'ISO22301:logout' %}" method="POST">
        {% csrf_token %}
        <button type="submit">Log Out</button>
    </form>
</html>

templates/ISO22301/bye.html

<html>
    <meta http-equiv="refresh" content="3;url={% url 'ISO22301:login' %}"/> 
    <h3>You have logged out</h3>
    <p>After 3 seconds you will be redirected to the login page</p>
</html>

the last template above uses the <meta/> tag to achieve a 3-secs delay before the page redirects back to the login page. The Logout view redirects to the last page above as per LOGOUT_REDIRECT_URL = "ISO22301:bye", then bye.html automatically redirects to the login page as described above.

Finally, you wanted to format the error messages. Fortunately, Django’s native views already render error messages. You only need to format those. The HTML structure of the messages are as follows:

<ul class="errorlist">
    <li>message</li> 
    <li>message</li> 
</ul>

Therefore, all you need is CSS targetting the relevant HTML elements

/static/css/styles.css

.errorlist li {
    color: tan;
    font-weight: bold;
    background: lightyellow;
    height: 30px;
    list-style-type: none;
    text-align: center;
    vertical-align: center;
}

I hope the above helps you.

1 Like

Thanks, I’ve made the changes.
I’ll checkout your suggestion, I need to refamilarize myself with current HTML and good practices after not doing any serious HTML work in 20 some years. I did develop bad habits, such as using

as a spacer and headers for font sizing only.

Thanks, it was very helpful in restructuring my code.

I made a few changes as using ISO22301:logout format didn’t work, but changing the redirects and using just “logout”, for example, resulted in the proper paths.

The Login as views and bye redirect were most helpful, not sure why I couldn’t get them to work but now everything works fine.

Still having a wierd favIcon issue. The home template renders it properly but the dashboard one doesn’t, even though both extend layout.

Those snippets were taken from a functional replication. The code samples are from an actual project created in Django 5.2 as a test case. The tree above is real. If something is not working your end, you probably abandoned vital parts of the snippets. Just use the above (verbatim) to start another Django project exclusively and see if it fails. If not, study it – learn from it.

We may only fully understand your scenario when you share relevant code. Adapting suggestions and reporting new (unrelated) issues will only make this thread confusing for those may have similar issues in the future.

Finally, if your major problem has been resolved, consider marking this thread as “Solved”

Thanks. I’ll try your suggestion to see what I did wrong, and figure out the difference between the : and / settings.
I really appreciated the help.