feat: moved users views to web app

This commit is contained in:
2024-11-30 13:22:09 +01:00
parent 500886aacd
commit e40cdee31b
14 changed files with 121 additions and 128 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
from django.shortcuts import render
from django.views.generic import TemplateView
from shop.filters import ProductFilter
from shop.models import Product, Cart
from shop.models import Product, Cart, ProductPrice
from web.mixins import FilteredQuerysetMixin, PaginatedQuerysetMixin
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
from web.utils import get_or_create_cart
@@ -9,7 +9,7 @@ from web.utils import get_or_create_cart
class ListProducts(TemplateView, FilteredQuerysetMixin, PaginatedQuerysetMixin):
template_name = "web/list_products.html"
queryset = Product.objects.all()
queryset = Product.objects.all().prefetch_related('images', )
filter_class = ProductFilter
def get_context_data(self, **kwargs):
@@ -27,8 +27,8 @@
<!-- Cart -->
<div id="myCartDropdown1"
class="absolute top-10 hidden z-10 mx-auto max-w-sm space-y-4 overflow-hidden rounded-lg bg-white p-4 antialiased shadow-lg dark:bg-gray-800">
class="absolute top-10 z-10 mx-auto max-w-sm space-y-4 overflow-hidden rounded-lg bg-white p-4 antialiased shadow-lg dark:bg-gray-800 hidden"
>
<div>
{% if cart.items.count == 0 %}
<h2 class="text-sm font-medium text-gray-900 dark:text-white">No hay nada en el carrito</h2>
+1 -1
View File
@@ -76,7 +76,7 @@
{% include 'components/users/user_dropdown.html' %}
</div>
{% else %}
<a href="{% url 'users:login' %}"
<a href="{% url 'web:login' %}"
class="inline-flex items-center rounded-lg justify-center p-2 hover:bg-gray-100 dark:hover:bg-gray-700 text-sm font-medium leading-none text-gray-900 dark:text-white">Inicia
sesión</a>
{% endif %}
@@ -39,7 +39,7 @@
</ul>
<div class="p-2 text-sm font-medium text-gray-900 dark:text-white">
<a href="{% url 'users:logout' %}" title=""
<a href="{% url 'web:logout' %}" title=""
class="inline-flex w-full items-center gap-2 rounded-md px-3 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-600">
{% translate 'Cerrar sesión' %}
</a>
+56
View File
@@ -0,0 +1,56 @@
{% extends 'theme/base.html' %}
{% block main %}
<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">
<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">
Shoppy
</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">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Sign in to your account
</h1>
<form class="space-y-4 md:space-y-6" action="{% url 'web:login' %}" method="post">
{% csrf_token %}
{% for field in form %}
<label for="{{ field.id_for_label }}"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">
{{ field.label }}
</label>
{{ field }}
{% endfor %}
{# <div class="flex items-center justify-between">#}
{# <div class="flex items-start">#}
{# <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">#}
{# </div>#}
{# <div class="ml-3 text-sm">#}
{# <label for="remember" class="text-gray-500 dark:text-gray-300">Remember me</label>#}
{# </div>#}
{# </div>#}
{# <a href="#" class="text-sm font-medium text-primary-600 hover:underline dark:text-primary-500">#}
{# Forgot password?#}
{# </a>#}
{# </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">
Dont 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>
</section>
{% endblock %}
+45
View File
@@ -0,0 +1,45 @@
{% extends 'base/base.html' %}
{% block content %}
<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">
<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">
Flowbite
</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">
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Create and account
</h1>
<form class="space-y-4 md:space-y-6" action="#">
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
<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="">
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Password</label>
<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="">
</div>
<div>
<label for="confirm-password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm password</label>
<input type="confirm-password" name="confirm-password" id="confirm-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="">
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="terms" aria-describedby="terms" 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>
<div class="ml-3 text-sm">
<label for="terms" class="font-light text-gray-500 dark:text-gray-300">I accept the <a class="font-medium text-primary-600 hover:underline dark:text-primary-500" href="#">Terms and Conditions</a></label>
</div>
</div>
<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">Create an account</button>
<p class="text-sm font-light text-gray-500 dark:text-gray-400">
Already have an account? <a href="#" class="font-medium text-primary-600 hover:underline dark:text-primary-500">Login here</a>
</p>
</form>
</div>
</div>
</div>
</section>
{% endblock %}
+42
View File
@@ -0,0 +1,42 @@
{% extends 'base/base.html' %}
{% block content %}
<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">
<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">
Shoppy
</a>
<div class="w-full p-6 bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md dark:bg-gray-800 dark:border-gray-700 sm:p-8">
<h2 class="mb-1 text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Change Password
</h2>
<form class="mt-4 space-y-4 lg:mt-5 md:space-y-5" action="#">
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
<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="">
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">New Password</label>
<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="">
</div>
<div>
<label for="confirm-password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Confirm password</label>
<input type="confirm-password" name="confirm-password" id="confirm-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="">
</div>
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="newsletter" aria-describedby="newsletter" 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>
<div class="ml-3 text-sm">
<label for="newsletter" class="font-light text-gray-500 dark:text-gray-300">I accept the <a class="font-medium text-primary-600 hover:underline dark:text-primary-500" href="#">Terms and Conditions</a></label>
</div>
</div>
<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">Reset passwod</button>
</form>
</div>
</div>
</section>
{% endblock %}
+6 -32
View File
@@ -14,13 +14,13 @@
<img class="w-full hidden dark:block" src="{{ product.images.all|first }}" alt=""/>
</div>
<div class="mt-6 sm:mt-8 lg:mt-0">
<div class="my-4 sm:m-8 lg:m-0">
<h1
class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white"
>
{{ product.name }}
</h1>
<div class="mt-4 sm:items-center sm:gap-4 sm:flex">
<div class="my-4 sm:items-center sm:gap-4 sm:flex">
<p
class="text-2xl font-extrabold text-gray-900 sm:text-3xl dark:text-white"
>
@@ -109,7 +109,7 @@
</div>
</div>
<div class="mt-6 sm:gap-4 sm:items-center sm:flex sm:mt-8">
<div class="my-4 sm:gap-4 sm:items-center sm:flex sm:m-8">
<a
href="#"
title=""
@@ -136,41 +136,15 @@
{% translate 'Añadir a lista de deseados' %}
</a>
<a
href="#"
title=""
class="text-white mt-4 sm:mt-0 bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800 flex items-center justify-center"
role="button"
>
<svg
class="w-5 h-5 -ms-2 me-2"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 4h1.5L8 16m0 0h8m-8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4Zm8 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4Zm.75-3H7.5M11 7H6.312M17 4v6m-3-3h6"
/>
</svg>
{% include 'components/cart/add_to_cart.html' %}
<hr class="my-6 md:my-8 border-gray-200 dark:border-gray-800"/>
{% translate 'Añadir al carrito' %}
</a>
</div>
<hr class="my-6 md:my-8 border-gray-200 dark:border-gray-800"/>
<p class="mb-6 text-gray-500 dark:text-gray-400">
{{ product.description }}
</p>
</div>
</div>
</div>
</section>
{% endblock %}
+98
View File
@@ -0,0 +1,98 @@
from django.contrib.auth import get_user_model
from django.shortcuts import reverse
from rest_framework import status
from rest_framework.test import APITestCase
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("web:login"),
{
"username": self.user.username,
"password": self.password,
},
)
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
def test_login_get(self):
response = self.client.get(reverse("web:login"))
assert response.status_code == status.HTTP_200_OK
def test_login_already_logged_in(self):
response = self.client.post(
reverse("web:login"),
{
"username": self.user.username,
"password": self.password,
},
)
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
response = self.client.get(reverse("web:login"))
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
def test_login_failed(self):
response = self.client.post(
reverse("web:login"),
{
"username": self.user.username,
"password": "wrongpassword",
},
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
def test_login_then_logout(self):
response = self.client.post(
reverse("web:login"),
{
"username": self.user.username,
"password": self.password,
},
)
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
response = self.client.post(reverse("web:logout"))
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
def test_logout_already_logged_out(self):
response = self.client.post(reverse("web:logout"))
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
def test_login_then_get_logout(self):
response = self.client.post(
reverse("web:login"),
{
"username": self.user.username,
"password": self.password,
},
)
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
response = self.client.get(reverse("web:logout"))
assert response.status_code == status.HTTP_302_FOUND
assert response.url == reverse("web:index")
+9 -1
View File
@@ -1,6 +1,6 @@
from django.urls import path
from web.views import index, product_detail, add_cart_item, delete_cart_item
from web.views import index, product_detail, add_cart_item, delete_cart_item, login, logout, register, reset_password
from web.components.views import list_products, cart_dropdown
app_name = "web"
@@ -9,9 +9,17 @@ app_name = "web"
urlpatterns = [
path("", index, name="index"),
path("products/<int:pk>/<str:slug>/", product_detail, name="product_detail"),
# users
path("login/", login, name="login"),
path("logout/", logout, name="logout"),
path("register/", register, name="register"),
path("reset-password/", reset_password, name="reset-password"),
# components
path("web/components/products/", list_products, name="list_products"),
path("web/components/cart-dropdown/", cart_dropdown, name="cart_dropdown"),
# api
path("web/add-cart-item/", add_cart_item, name="add_cart_item"),
path("web/delete-cart-item/<int:pk>/", delete_cart_item, name="delete_cart_item"),
+70 -2
View File
@@ -1,13 +1,76 @@
from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView
from django.http.response import HttpResponse
from django.views.decorators.http import require_http_methods
from shop.models import Product, Cart, CartItem
from shop.models import Product, CartItem
from web.utils import get_or_create_cart
from web.settings import ANONYMOUS_CART_ID_COOKIE_NAME
from django.contrib.auth import login as login_user
from django.contrib.auth import logout as logout_user
from django.shortcuts import redirect, render, reverse
from django.views.generic import TemplateView, View
from users.forms import LoginForm
class RedirectionMixin:
redirect_to = ""
def get_redirection(self):
return reverse(self.redirect_to)
class LoginView(View, RedirectionMixin):
template_name = "users/login.html"
redirect_to = "web: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())
if not login_form.cleaned_data.get('remember_me'):
request.session.set_expiry(0)
return redirect(self.get_redirection())
return render(request, self.template_name, {"form": login_form}, status=400)
class LogoutView(View, RedirectionMixin):
redirect_to = "web: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):
template_name = "web/register.html"
class ResetPasswordView(TemplateView):
template_name = "web/reset-password.html"
class IndexView(TemplateView):
template_name = "web/index.html"
@@ -76,3 +139,8 @@ def delete_cart_item(request, pk, *args, **kwargs):
index = IndexView.as_view()
product_detail = ProductDetail.as_view()
login = LoginView.as_view()
logout = LogoutView.as_view()
register = RegisterView.as_view()
reset_password = ResetPasswordView.as_view()