feat: login view
This commit is contained in:
@@ -27,6 +27,12 @@ python manage.py migrate
|
|||||||
python manage.py runserver
|
python manage.py runserver
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Tailwind
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python manage.py tailwind start
|
||||||
|
```
|
||||||
|
|
||||||
### Tareas de Celery
|
### Tareas de Celery
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ urlpatterns = [
|
|||||||
"swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"
|
"swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"
|
||||||
),
|
),
|
||||||
path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
||||||
path("shop/", include("shop.api.v1.routers", namespace="shop")),
|
path("shop/", include("shop.api.v1.routers", namespace="shop_api")),
|
||||||
path("files/", include("files.api.v1.urls", namespace="files")),
|
path("files/", include("files.api.v1.urls", namespace="files")),
|
||||||
path("auth/", include("users.api.v1.urls", namespace="auth")),
|
path("auth/", include("users.api.v1.urls", namespace="auth")),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.urls import path, include
|
|||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("", include("shop.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/v1/", include("config.api.v1.urls")),
|
path("api/v1/", include("config.api.v1.urls")),
|
||||||
path("watchman/", include("watchman.urls")),
|
path("watchman/", include("watchman.urls")),
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from shop.views import index
|
||||||
|
|
||||||
|
app_name = "shop"
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("", index, name="index"),
|
||||||
|
]
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
from django.http.response import HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
def index(request, *args, **kwargs):
|
||||||
|
return HttpResponse("Hello there")
|
||||||
|
|||||||
@@ -4,54 +4,75 @@
|
|||||||
* If you need the full config, get it from here:
|
* If you need the full config, get it from here:
|
||||||
* https://unpkg.com/browse/tailwindcss@latest/stubs/defaultConfig.stub.js
|
* https://unpkg.com/browse/tailwindcss@latest/stubs/defaultConfig.stub.js
|
||||||
*/
|
*/
|
||||||
|
const colors = require('tailwindcss/colors')
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
/**
|
/**
|
||||||
* HTML. Paths to Django template files that will contain Tailwind CSS classes.
|
* HTML. Paths to Django template files that will contain Tailwind CSS classes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Templates within theme app (<tailwind_app_name>/templates), e.g. base.html. */
|
/* Templates within theme app (<tailwind_app_name>/templates), e.g. base.html. */
|
||||||
'../templates/**/*.html',
|
'../templates/**/*.html',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Main templates directory of the project (BASE_DIR/templates).
|
* Main templates directory of the project (BASE_DIR/templates).
|
||||||
* Adjust the following line to match your project structure.
|
* Adjust the following line to match your project structure.
|
||||||
*/
|
*/
|
||||||
'../../templates/**/*.html',
|
'../../templates/**/*.html',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Templates in other django apps (BASE_DIR/<any_app_name>/templates).
|
* Templates in other django apps (BASE_DIR/<any_app_name>/templates).
|
||||||
* Adjust the following line to match your project structure.
|
* Adjust the following line to match your project structure.
|
||||||
*/
|
*/
|
||||||
'../../**/templates/**/*.html',
|
'../../**/templates/**/*.html',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JS: If you use Tailwind CSS in JavaScript, uncomment the following lines and make sure
|
* JS: If you use Tailwind CSS in JavaScript, uncomment the following lines and make sure
|
||||||
* patterns match your project structure.
|
* patterns match your project structure.
|
||||||
*/
|
*/
|
||||||
/* JS 1: Ignore any JavaScript in node_modules folder. */
|
/* JS 1: Ignore any JavaScript in node_modules folder. */
|
||||||
// '!../../**/node_modules',
|
// '!../../**/node_modules',
|
||||||
/* JS 2: Process all JavaScript files in the project. */
|
/* JS 2: Process all JavaScript files in the project. */
|
||||||
// '../../**/*.js',
|
// '../../**/*.js',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Python: If you use Tailwind CSS classes in Python, uncomment the following line
|
* Python: If you use Tailwind CSS classes in Python, uncomment the following line
|
||||||
* and make sure the pattern below matches your project structure.
|
* and make sure the pattern below matches your project structure.
|
||||||
*/
|
*/
|
||||||
// '../../**/*.py'
|
// '../../**/*.py'
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {
|
||||||
},
|
colors: {
|
||||||
plugins: [
|
gray: colors.zinc,
|
||||||
/**
|
accent: colors.teal,
|
||||||
* '@tailwindcss/forms' is the forms plugin that provides a minimal styling
|
primary: colors.rose,
|
||||||
* for forms. If you don't like it or have own styling for forms,
|
secondary: {
|
||||||
* comment the line below to disable '@tailwindcss/forms'.
|
50: '#d1daf5',
|
||||||
*/
|
100: '#bac3e0',
|
||||||
require('@tailwindcss/forms'),
|
200: '#a4accc',
|
||||||
require('@tailwindcss/typography'),
|
300: '#8e96b8',
|
||||||
require('@tailwindcss/aspect-ratio'),
|
400: '#7a81a4',
|
||||||
],
|
500: '#656c91',
|
||||||
|
DEFAULT: '#52587e',
|
||||||
|
600: '#3f446b',
|
||||||
|
700: '#2c3259',
|
||||||
|
800: '#192047',
|
||||||
|
900: '#070e36'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
/**
|
||||||
|
* '@tailwindcss/forms' is the forms plugin that provides a minimal styling
|
||||||
|
* for forms. If you don't like it or have own styling for forms,
|
||||||
|
* comment the line below to disable '@tailwindcss/forms'.
|
||||||
|
*/
|
||||||
|
require('@tailwindcss/forms'),
|
||||||
|
require('@tailwindcss/typography'),
|
||||||
|
require('@tailwindcss/aspect-ratio'),
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{{ title }}</title>
|
<title>{{ title }}</title>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
|
||||||
|
|
||||||
{% tailwind_css %}
|
{% tailwind_css %}
|
||||||
|
{% block extra_js %}
|
||||||
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .login import *
|
||||||
|
from .register import *
|
||||||
|
from .reset_password import *
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from django.utils.text import gettext_lazy as _
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import AuthenticationForm, UsernameField
|
||||||
|
|
||||||
|
|
||||||
|
class LoginForm(AuthenticationForm):
|
||||||
|
username = UsernameField(
|
||||||
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"autofocus": True,
|
||||||
|
"class": "bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600"
|
||||||
|
"focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600"
|
||||||
|
"dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
password = forms.CharField(
|
||||||
|
label=_("Password"),
|
||||||
|
strip=False,
|
||||||
|
widget=forms.PasswordInput(
|
||||||
|
attrs={
|
||||||
|
"autocomplete": "current-password",
|
||||||
|
"class": "bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 "
|
||||||
|
"focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 "
|
||||||
|
"dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,45 +1,56 @@
|
|||||||
{% extends 'theme/base.html' %}
|
{% extends 'theme/base.html' %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
||||||
<section class="bg-gray-50 dark:bg-gray-900">
|
<section class="bg-gray-50 dark:bg-gray-900">
|
||||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
||||||
|
|
||||||
<a href="#" class="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
|
<a href="#" class="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
|
||||||
<img class="w-8 h-8 mr-2" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/logo.svg" alt="logo">
|
<img class="w-8 h-8 mr-2" src="https://flowbite.s3.amazonaws.com/blocks/marketing-ui/logo.svg" alt="logo">
|
||||||
Shoppy
|
Shoppy
|
||||||
</a>
|
</a>
|
||||||
<div class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
|
|
||||||
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
<div
|
||||||
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
class="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
|
||||||
Sign in to your account
|
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
||||||
</h1>
|
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
||||||
<form class="space-y-4 md:space-y-6" action="#">
|
Sign in to your account
|
||||||
<div>
|
</h1>
|
||||||
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
|
<form class="space-y-4 md:space-y-6" action="{% url 'users:login' %}" method="post">
|
||||||
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" required="">
|
{% csrf_token %}
|
||||||
</div>
|
{% for field in form %}
|
||||||
<div>
|
<label for="{{ field.id_for_label }}"
|
||||||
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
|
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||||
<input type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required="">
|
{{ field.label }}
|
||||||
</div>
|
</label>
|
||||||
<div class="flex items-center justify-between">
|
{{ field }}
|
||||||
<div class="flex items-start">
|
{% endfor %}
|
||||||
<div class="flex items-center h-5">
|
|
||||||
<input id="remember" aria-describedby="remember" type="checkbox" class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800" required="">
|
<div class="flex items-center justify-between">
|
||||||
</div>
|
<div class="flex items-start">
|
||||||
<div class="ml-3 text-sm">
|
<div class="flex items-center h-5">
|
||||||
<label for="remember" class="text-gray-500 dark:text-gray-300">Remember me</label>
|
<input id="remember" aria-describedby="remember" type="checkbox"
|
||||||
</div>
|
class="w-4 h-4 border border-gray-300 rounded bg-gray-50 focus:ring-3 focus:ring-primary-300 dark:bg-gray-700 dark:border-gray-600 dark:focus:ring-primary-600 dark:ring-offset-gray-800">
|
||||||
</div>
|
</div>
|
||||||
<a href="#" class="text-sm font-medium text-primary-600 hover:underline dark:text-primary-500">Forgot password?</a>
|
<div class="ml-3 text-sm">
|
||||||
</div>
|
<label for="remember" class="text-gray-500 dark:text-gray-300">Remember me</label>
|
||||||
<button type="submit" class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">Sign in</button>
|
</div>
|
||||||
<p class="text-sm font-light text-gray-500 dark:text-gray-400">
|
</div>
|
||||||
Don’t have an account yet? <a href="#" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
|
<a href="#" class="text-sm font-medium text-primary-600 hover:underline dark:text-primary-500">
|
||||||
</p>
|
Forgot password?
|
||||||
</form>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
class="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
<p class="text-sm font-light text-gray-500 dark:text-gray-400">
|
||||||
|
Don’t have an account yet?
|
||||||
|
<a href="#" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Sign up</a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ class TestChangePassword(APITestCase):
|
|||||||
|
|
||||||
def test_login_then_change_password(self):
|
def test_login_then_change_password(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/v1/auth/login/",
|
reverse("auth:login"),
|
||||||
{
|
{
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
@@ -29,7 +30,7 @@ class TestChangePassword(APITestCase):
|
|||||||
jwt_token = response.data.get("access")
|
jwt_token = response.data.get("access")
|
||||||
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
"/api/v1/auth/change-password/",
|
reverse("auth:change_password"),
|
||||||
{
|
{
|
||||||
"new_password": "barad-dur",
|
"new_password": "barad-dur",
|
||||||
"new_password2": "barad-dur",
|
"new_password2": "barad-dur",
|
||||||
@@ -41,7 +42,7 @@ class TestChangePassword(APITestCase):
|
|||||||
|
|
||||||
def test_login_then_change_password_with_wrong_password(self):
|
def test_login_then_change_password_with_wrong_password(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/v1/auth/login/",
|
reverse("auth:login"),
|
||||||
{
|
{
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
@@ -52,7 +53,7 @@ class TestChangePassword(APITestCase):
|
|||||||
jwt_token = response.data.get("access")
|
jwt_token = response.data.get("access")
|
||||||
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
"/api/v1/auth/change-password/",
|
reverse("auth:change_password"),
|
||||||
{
|
{
|
||||||
"new_password": "barad-dur",
|
"new_password": "barad-dur",
|
||||||
"new_password2": "barad-dur",
|
"new_password2": "barad-dur",
|
||||||
@@ -64,7 +65,7 @@ class TestChangePassword(APITestCase):
|
|||||||
|
|
||||||
def test_login_then_change_password_mismatch_password(self):
|
def test_login_then_change_password_mismatch_password(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/v1/auth/login/",
|
reverse("auth:login"),
|
||||||
{
|
{
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
@@ -75,7 +76,7 @@ class TestChangePassword(APITestCase):
|
|||||||
jwt_token = response.data.get("access")
|
jwt_token = response.data.get("access")
|
||||||
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
"/api/v1/auth/change-password/",
|
reverse("auth:change_password"),
|
||||||
{
|
{
|
||||||
"new_password": "barad-dur",
|
"new_password": "barad-dur",
|
||||||
"new_password2": "mountdoom",
|
"new_password2": "mountdoom",
|
||||||
@@ -87,7 +88,7 @@ class TestChangePassword(APITestCase):
|
|||||||
|
|
||||||
def test_login_then_change_password_but_its_the_same(self):
|
def test_login_then_change_password_but_its_the_same(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/v1/auth/login/",
|
reverse("auth:login"),
|
||||||
{
|
{
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
@@ -98,7 +99,7 @@ class TestChangePassword(APITestCase):
|
|||||||
jwt_token = response.data.get("access")
|
jwt_token = response.data.get("access")
|
||||||
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
||||||
response = self.client.put(
|
response = self.client.put(
|
||||||
"/api/v1/auth/change-password/",
|
reverse("auth:change_password"),
|
||||||
{
|
{
|
||||||
"new_password": self.password,
|
"new_password": self.password,
|
||||||
"new_password2": self.password,
|
"new_password2": self.password,
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class TestLogin(APITestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.password = "theonering"
|
||||||
|
self.user = User.objects.create(
|
||||||
|
username="sauron",
|
||||||
|
email="sauron@mordor.middleearth",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.user.set_password(self.password)
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
def test_login(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("auth:login"),
|
||||||
|
{
|
||||||
|
"username": self.user.username,
|
||||||
|
"password": self.password,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
assert response.data.get("access") is not None
|
||||||
|
assert response.data.get("refresh") is not None
|
||||||
|
|
||||||
|
def test_login_then_verify(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("auth:login"),
|
||||||
|
{
|
||||||
|
"username": self.user.username,
|
||||||
|
"password": self.password,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
jwt_token = response.data.get("access")
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("auth:verify_jwt"),
|
||||||
|
{
|
||||||
|
"token": jwt_token,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
def test_login_then_refresh(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("auth:login"),
|
||||||
|
{
|
||||||
|
"username": self.user.username,
|
||||||
|
"password": self.password,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
jwt_token = response.data.get("refresh")
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("auth:refresh_jwt"),
|
||||||
|
{
|
||||||
|
"refresh": jwt_token,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
assert response.data.get("access") is not None
|
||||||
|
assert response.data.get("access") != jwt_token
|
||||||
|
|
||||||
|
def test_login_then_get_user_info(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("auth:login"),
|
||||||
|
{
|
||||||
|
"username": self.user.username,
|
||||||
|
"password": self.password,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
jwt_token = response.data.get("access")
|
||||||
|
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
||||||
|
response = self.client.get(reverse("auth:user_info"))
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
assert response.data.get("email") == self.user.email
|
||||||
|
|
||||||
|
def test_login_failed(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("auth:login"),
|
||||||
|
{
|
||||||
|
"username": self.user.username,
|
||||||
|
"password": "wrongpassword",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
+38
-56
@@ -1,6 +1,7 @@
|
|||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@@ -18,83 +19,64 @@ class TestLogin(APITestCase):
|
|||||||
|
|
||||||
def test_login(self):
|
def test_login(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/v1/auth/login/",
|
reverse("users:login"),
|
||||||
{
|
{
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_302_FOUND
|
||||||
assert response.data.get("access") is not None
|
assert response.url == reverse("shop:index")
|
||||||
assert response.data.get("refresh") is not None
|
|
||||||
|
|
||||||
def test_login_then_verify(self):
|
def test_login_get(self):
|
||||||
|
response = self.client.get(reverse("users:login"))
|
||||||
|
assert response.status_code == status.HTTP_200_OK
|
||||||
|
|
||||||
|
def test_login_already_logged_in(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/v1/auth/login/",
|
reverse("users:login"),
|
||||||
{
|
{
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"password": self.password,
|
"password": self.password,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_302_FOUND
|
||||||
jwt_token = response.data.get("access")
|
assert response.url == reverse("shop:index")
|
||||||
response = self.client.post(
|
|
||||||
"/api/v1/auth/verify/",
|
|
||||||
{
|
|
||||||
"token": jwt_token,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
response = self.client.get(reverse("users:login"))
|
||||||
|
assert response.status_code == status.HTTP_302_FOUND
|
||||||
def test_login_then_refresh(self):
|
assert response.url == reverse("shop:index")
|
||||||
response = self.client.post(
|
|
||||||
"/api/v1/auth/login/",
|
|
||||||
{
|
|
||||||
"username": self.user.username,
|
|
||||||
"password": self.password,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
|
||||||
jwt_token = response.data.get("refresh")
|
|
||||||
response = self.client.post(
|
|
||||||
"/api/v1/auth/refresh/",
|
|
||||||
{
|
|
||||||
"refresh": jwt_token,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
|
||||||
assert response.data.get("access") is not None
|
|
||||||
assert response.data.get("access") != jwt_token
|
|
||||||
|
|
||||||
def test_login_then_get_user_info(self):
|
|
||||||
response = self.client.post(
|
|
||||||
"/api/v1/auth/login/",
|
|
||||||
{
|
|
||||||
"username": self.user.username,
|
|
||||||
"password": self.password,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
|
||||||
jwt_token = response.data.get("access")
|
|
||||||
self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {jwt_token}")
|
|
||||||
response = self.client.get("/api/v1/auth/me/")
|
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_200_OK
|
|
||||||
assert response.data.get("email") == self.user.email
|
|
||||||
|
|
||||||
def test_login_failed(self):
|
def test_login_failed(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
"/api/v1/auth/login/",
|
reverse("users:login"),
|
||||||
{
|
{
|
||||||
"username": self.user.username,
|
"username": self.user.username,
|
||||||
"password": "wrongpassword",
|
"password": "wrongpassword",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||||
|
|
||||||
|
def test_login_then_logout(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("users:login"),
|
||||||
|
{
|
||||||
|
"username": self.user.username,
|
||||||
|
"password": self.password,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_302_FOUND
|
||||||
|
assert response.url == reverse("shop:index")
|
||||||
|
|
||||||
|
response = self.client.post(reverse("users:logout"))
|
||||||
|
assert response.status_code == status.HTTP_302_FOUND
|
||||||
|
assert response.url == reverse("shop:index")
|
||||||
|
|
||||||
|
def test_logout_already_logged_out(self):
|
||||||
|
response = self.client.post(reverse("users:logout"))
|
||||||
|
assert response.status_code == status.HTTP_302_FOUND
|
||||||
|
assert response.url == reverse("shop:index")
|
||||||
|
|||||||
+2
-1
@@ -1,11 +1,12 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
from users.views import login, register, reset_password
|
from users.views import login, logout, register, reset_password
|
||||||
|
|
||||||
app_name = "users"
|
app_name = "users"
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("login/", login, name="login"),
|
path("login/", login, name="login"),
|
||||||
|
path("logout/", logout, name="logout"),
|
||||||
path("register/", register, name="register"),
|
path("register/", register, name="register"),
|
||||||
path("reset-password/", reset_password, name="reset-password"),
|
path("reset-password/", reset_password, name="reset-password"),
|
||||||
]
|
]
|
||||||
|
|||||||
+50
-5
@@ -1,18 +1,63 @@
|
|||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView, View
|
||||||
|
from django.shortcuts import render, reverse, redirect
|
||||||
|
from users.forms import LoginForm
|
||||||
|
from django.contrib.auth import login as login_user, logout as logout_user
|
||||||
|
|
||||||
|
|
||||||
class LoginView(TemplateView):
|
class RedirectionMixin:
|
||||||
template_name = 'users/login.html'
|
redirect_to = ""
|
||||||
|
|
||||||
|
def get_redirection(self):
|
||||||
|
return reverse(self.redirect_to)
|
||||||
|
|
||||||
|
|
||||||
|
class LoginView(View, RedirectionMixin):
|
||||||
|
template_name = "users/login.html"
|
||||||
|
redirect_to = "shop:index"
|
||||||
|
login_form_class = LoginForm
|
||||||
|
|
||||||
|
def get_login_form(self, request):
|
||||||
|
return self.login_form_class(data=request.POST)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
return redirect(self.get_redirection())
|
||||||
|
|
||||||
|
login_form = self.login_form_class()
|
||||||
|
return render(request, self.template_name, {"form": login_form})
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
login_form = self.get_login_form(request)
|
||||||
|
|
||||||
|
if login_form.is_valid():
|
||||||
|
login_user(request, login_form.get_user())
|
||||||
|
|
||||||
|
return redirect(self.get_redirection())
|
||||||
|
|
||||||
|
return render(request, self.template_name, {"form": login_form}, status=400)
|
||||||
|
|
||||||
|
|
||||||
|
class LogoutView(View, RedirectionMixin):
|
||||||
|
redirect_to = "shop:index"
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
logout_user(request)
|
||||||
|
return redirect(self.get_redirection())
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
logout_user(request)
|
||||||
|
return redirect(self.get_redirection())
|
||||||
|
|
||||||
|
|
||||||
class RegisterView(TemplateView):
|
class RegisterView(TemplateView):
|
||||||
template_name = 'users/register.html'
|
template_name = "users/register.html"
|
||||||
|
|
||||||
|
|
||||||
class ResetPasswordView(TemplateView):
|
class ResetPasswordView(TemplateView):
|
||||||
template_name = 'users/reset-password.html'
|
template_name = "users/reset-password.html"
|
||||||
|
|
||||||
|
|
||||||
login = LoginView.as_view()
|
login = LoginView.as_view()
|
||||||
|
logout = LogoutView.as_view()
|
||||||
register = RegisterView.as_view()
|
register = RegisterView.as_view()
|
||||||
reset_password = ResetPasswordView.as_view()
|
reset_password = ResetPasswordView.as_view()
|
||||||
|
|||||||
Reference in New Issue
Block a user