Lowlights 2024-02-24 12:59:47 +06:00
parent a387b64af9
commit 3f86d8d822
40 changed files with 269 additions and 48 deletions

View File

@ -9,19 +9,24 @@ class Cart:
Initialize the cart. Initialize the cart.
""" """
self.session = request.session self.session = request.session
cart = self.session.get(settings.CART_SESSION_ID) user_id = self.session.get('keycloak_user_id') # Используйте идентификатор пользователя из сессии
if user_id:
cart_session_id = f"{settings.CART_SESSION_ID}_{user_id}" # Создайте уникальный ключ сессии для корзины
else:
cart_session_id = settings.CART_SESSION_ID
cart = self.session.get(cart_session_id)
if not cart: if not cart:
# save an empty cart in the session # save an empty cart in the session
cart = self.session[settings.CART_SESSION_ID] = {} cart = self.session[cart_session_id] = {}
self.cart = cart self.cart = cart
self.cart_session_id = cart_session_id
def __iter__(self): def __iter__(self):
""" """
Iterate over the items in the cart and get the products Iterate over the items in the cart and get the products
from the database. from the database.
""" """
product_ids = self.cart.keys() product_ids = self.cart.keys() # get the product objects and add them to the cart
# get the product objects and add them to the cart
products = Product.objects.filter(id__in=product_ids) products = Product.objects.filter(id__in=product_ids)
cart = self.cart.copy() cart = self.cart.copy()
for product in products: for product in products:
@ -55,6 +60,8 @@ class Cart:
# mark the session as "modified" to make sure it gets saved # mark the session as "modified" to make sure it gets saved
self.session.modified = True self.session.modified = True
self.session[self.cart_session_id] = self.cart
def remove(self, product): def remove(self, product):
""" """
Remove a product from the cart. Remove a product from the cart.

Binary file not shown.

15
django.log Normal file
View File

@ -0,0 +1,15 @@
INFO 2024-02-23 13:45:46,251 autoreload Watching for file changes with StatReloader
INFO 2024-02-23 13:46:13,396 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:46:43,771 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:46:48,385 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:46:48,633 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:46:48,830 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:48:22,691 autoreload Watching for file changes with StatReloader
INFO 2024-02-23 13:48:32,058 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:48:37,176 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:48:37,585 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:48:44,312 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 13:48:51,761 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 21:15:16,575 autoreload Watching for file changes with StatReloader
INFO 2024-02-23 21:15:27,243 views Redirecting to Keycloak for authentication.
INFO 2024-02-23 21:15:32,818 views Redirecting to Keycloak for authentication.

View File

@ -25,7 +25,7 @@ SECRET_KEY = 'django-insecure-)p-ed99i+^)@_t33f65!y8eqn23!&kt)zc(ht$8h7e8#1k(o$f
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = True
ALLOWED_HOSTS = [] ALLOWED_HOSTS = ['127.0.0.1'] # Пример для разработки и продакшена
CART_SESSION_ID = 'cart' CART_SESSION_ID = 'cart'
# Application definition # Application definition
@ -33,6 +33,7 @@ CART_SESSION_ID = 'cart'
INSTALLED_APPS = [ INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.admin',
'django.contrib.auth', 'django.contrib.auth',
'django_extensions',
'django.contrib.contenttypes', 'django.contrib.contenttypes',
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
@ -40,6 +41,9 @@ INSTALLED_APPS = [
'main.apps.MainConfig', 'main.apps.MainConfig',
'cart.apps.CartConfig', 'cart.apps.CartConfig',
'orders.apps.OrdersConfig', 'orders.apps.OrdersConfig',
'users.apps.UsersConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -143,3 +147,42 @@ EMAIL_HOST_USER = 'fhjj3590@gmail.com'
EMAIL_HOST_PASSWORD = 'uzmd nmit yxwc sgox' # Предполагается, что это сгенерированный пароль для приложений EMAIL_HOST_PASSWORD = 'uzmd nmit yxwc sgox' # Предполагается, что это сгенерированный пароль для приложений
EMAIL_PORT = 587 EMAIL_PORT = 587
EMAIL_USE_TLS = True EMAIL_USE_TLS = True
KEYCLOAK_CONFIG = {
'SERVER_URL': 'https://auth.myterior.kz',
'REALM': 'Harmony',
'CLIENT_ID': 'lowlight',
'CLIENT_SECRET': 'u31Kvkj9V2npxdwJUODReO3YJ2w2iMul',
'CALLBACK_URL': 'http://127.0.0.1:8000/products/',
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': 'django.log',
'formatter': 'verbose',
},
},
'loggers': {
'': { # 'root' logger
'handlers': ['console', 'file'],
'level': 'INFO',
},
},
}

View File

@ -1,32 +1,15 @@
"""
URL configuration for Harmony project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin from django.contrib import admin
from django.urls import path,include from django.urls import path, include
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('cart/', include('cart.urls', namespace='cart')), path('cart/', include('cart.urls', namespace='cart')),
path('orders/', include('orders.urls', namespace='orders')), path('orders/', include('orders.urls', namespace='orders')),
path('', include('main.urls', namespace='main')), path('', include('main.urls', namespace='main')),
path('auth/', include('users.urls')), # Подключение URL приложения users
] ]
if settings.DEBUG: if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
document_root=settings.MEDIA_ROOT)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-02-22 09:03
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='product',
name='price',
field=models.IntegerField(max_length=19),
),
]

View File

@ -27,8 +27,7 @@ class Product(models.Model):
image = models.ImageField(upload_to='products/%Y/%m/%d', image = models.ImageField(upload_to='products/%Y/%m/%d',
blank=True) blank=True)
description = models.TextField(blank=True) description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, price = models.IntegerField(max_length=19)
decimal_places=2)
available = models.BooleanField(default=True) available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-facebook" viewBox="0 0 16 16">
<path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z"/>
</svg>

After

Width:  |  Height:  |  Size: 429 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-google" viewBox="0 0 16 16">
<path d="M15.545 6.558a9.42 9.42 0 0 1 .139 1.626c0 2.434-.87 4.492-2.384 5.885h.002C11.978 15.292 10.158 16 8 16A8 8 0 1 1 8 0a7.689 7.689 0 0 1 5.352 2.082l-2.284 2.284A4.347 4.347 0 0 0 8 3.166c-2.087 0-3.86 1.408-4.492 3.304a4.792 4.792 0 0 0 0 3.063h.003c.635 1.893 2.405 3.301 4.492 3.301 1.078 0 2.004-.276 2.722-.764h-.003a3.702 3.702 0 0 0 1.599-2.431H8v-3.08h7.545z"/>
</svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@ -48,22 +48,28 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link text-white" href="{% url 'cart:cart_detail' %}">Корзина</a> <a class="nav-link text-white" href="{% url 'cart:cart_detail' %}">Корзина</a>
</li> </li>
{# <li class="nav-item dropdown ">#}
{# <a class="nav-link dropdown-toggle text-white" href="#" role="button"#} <li class="nav-item">
{# data-bs-toggle="dropdown" aria-expanded="false">#} <a class="nav-link text-white" href="{% url 'keycloak_login' %}">Войти</a>
{# Мой профиль#} <li><a class="dropdown-item text-white" href="{% url 'profile' %}">Личный кабинет</a></li>
{# </a>#} </li>
{# <ul class="dropdown-menu">#} {% if request.session.keycloak_user_id %}
{# #} <li class="nav-item dropdown">
{# <li><a class="dropdown-item text-white" href="#">Личный кабинет</a>#} <a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{# </li>#} Мой профиль
{##} </a>
{# <li>#} <ul class="dropdown-menu">
{# <hr class="dropdown-divider">#}
{# </li>#}
{##} <li><hr class="dropdown-divider"></li>
{# </ul>#} <li><a class="dropdown-item text-white" href="{% url 'keycloak_logout' %}">Выйти</a></li>
{# </li>#} </ul>
</li>
{% endif %}
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -0,0 +1,7 @@
{% extends "main/base.html" %}
{% block content %}
<h1>Личный кабинет</h1>
<p>Привет, {{ username }}!</p>
{% endblock %}

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-02-22 09:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='orderitem',
name='price',
field=models.IntegerField(max_length=19),
),
]

View File

@ -29,7 +29,7 @@ class Order(models.Model):
class OrderItem(models.Model): class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE) order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
product = models.ForeignKey(Product, related_name='order_items', on_delete=models.CASCADE) product = models.ForeignKey(Product, related_name='order_items', on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2) price = models.IntegerField(max_length=19)
quantity = models.PositiveIntegerField(default=1) quantity = models.PositiveIntegerField(default=1)
def __str__(self): def __str__(self):

View File

@ -3,6 +3,7 @@ from .models import OrderItem, Order
from .forms import OrderCreateForm from .forms import OrderCreateForm
from cart.cart import Cart from cart.cart import Cart
from django.conf import settings from django.conf import settings
from decimal import Decimal
from .utils import send_invoice_via_email from .utils import send_invoice_via_email
@ -27,4 +28,8 @@ def order_create(request):
def order_created(request, order_id): def order_created(request, order_id):
order = get_object_or_404(Order, id=order_id) order = get_object_or_404(Order, id=order_id)
return render(request, 'orders/order/created.html', {'order': order}) return render(request, 'orders/order/created.html', {'order': order})

0
users/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

3
users/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
users/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class UsersConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'users'

View File

Binary file not shown.

3
users/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

3
users/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

10
users/urls.py Normal file
View File

@ -0,0 +1,10 @@
from django.urls import path
from .views import keycloak_login, keycloak_callback, keycloak_logout
from .views import profile
urlpatterns = [
path('login/', keycloak_login, name='keycloak_login'),
path('callback/', keycloak_callback, name='keycloak_callback'),
path('logout/', keycloak_logout, name='keycloak_logout'),
path('profile/', profile, name='profile'),
]

89
users/views.py Normal file
View File

@ -0,0 +1,89 @@
from django.shortcuts import redirect,render
from django.http import HttpResponse
from keycloak import KeycloakOpenID, KeycloakGetError
from django.conf import settings
import logging
# Настройка логгера для текущего модуля
logger = logging.getLogger(__name__)
# Функция для начала процесса аутентификации с Keycloak
def keycloak_login(request):
try:
keycloak_openid = KeycloakOpenID(
server_url=settings.KEYCLOAK_CONFIG['SERVER_URL'],
client_id=settings.KEYCLOAK_CONFIG['CLIENT_ID'],
realm_name=settings.KEYCLOAK_CONFIG['REALM'],
client_secret_key=settings.KEYCLOAK_CONFIG['CLIENT_SECRET']
)
auth_url = keycloak_openid.auth_url(redirect_uri=settings.KEYCLOAK_CONFIG['CALLBACK_URL'])
logger.info("Redirecting to Keycloak for authentication.")
return redirect(auth_url)
except Exception as e:
logger.error(f"Error during Keycloak login: {e}")
return HttpResponse("Ошибка при попытке аутентификации через Keycloak.")
# Функция обратного вызова для обработки ответа от Keycloak
def keycloak_callback(request):
code = request.GET.get('code')
if code:
try:
keycloak_openid = KeycloakOpenID(
server_url=settings.KEYCLOAK_CONFIG['SERVER_URL'],
client_id=settings.KEYCLOAK_CONFIG['CLIENT_ID'],
realm_name=settings.KEYCLOAK_CONFIG['REALM'],
client_secret_key=settings.KEYCLOAK_CONFIG['CLIENT_SECRET']
)
token = keycloak_openid.token(
grant_type=['authorization_code'],
code=code,
redirect_uri=settings.KEYCLOAK_CONFIG['CALLBACK_URL']
)
userinfo = keycloak_openid.userinfo(token['access_token'])
logger.info(f"User authenticated with Keycloak: {userinfo}")
request.session['keycloak_user_id'] = userinfo['sub']
request.session['username'] = userinfo.get('preferred_username', 'Guest')
return redirect('http://127.0.0.1:8000/products/')
except KeycloakGetError as e:
logger.error(f"Keycloak authentication error: {e}")
return HttpResponse("Ошибка аутентификации.")
except Exception as e:
logger.error(f"Unexpected error during Keycloak callback: {e}")
return HttpResponse("Неожиданная ошибка.")
else:
logger.warning("Authentication code not provided.")
return HttpResponse("Код аутентификации не предоставлен.")
# Функция для выхода из системы
def keycloak_logout(request):
try:
request.session.flush()
logger.info("User logged out.")
return redirect('http://127.0.0.1:8000/')
except Exception as e:
logger.error(f"Error during logout: {e}")
return HttpResponse("Ошибка при выходе из системы.")
def profile(request):
# Проверяем, аутентифицирован ли пользователь
if 'keycloak_user_id' in request.session:
# Извлекаем данные пользователя из сессии
user_id = request.session['keycloak_user_id']
username = request.session.get('username', 'Гость')
logger.info(f"Keycloak user ID: {request.session['keycloak_user_id']}, Username: {request.session['username']},{username}")
# Отображаем шаблон, передавая в него данные пользователя
return render(request, 'main/product/profile.html', {'username': username})
else:
# Если пользователь не аутентифицирован, перенаправляем на страницу входа
return redirect('keycloak_login')