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.
"""
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:
# 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_session_id = cart_session_id
def __iter__(self):
"""
Iterate over the items in the cart and get the products
from the database.
"""
product_ids = self.cart.keys()
# get the product objects and add them to the cart
product_ids = self.cart.keys() # get the product objects and add them to the cart
products = Product.objects.filter(id__in=product_ids)
cart = self.cart.copy()
for product in products:
@ -55,6 +60,8 @@ class Cart:
# mark the session as "modified" to make sure it gets saved
self.session.modified = True
self.session[self.cart_session_id] = self.cart
def remove(self, product):
"""
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!
DEBUG = True
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['127.0.0.1'] # Пример для разработки и продакшена
CART_SESSION_ID = 'cart'
# Application definition
@ -33,6 +33,7 @@ CART_SESSION_ID = 'cart'
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django_extensions',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
@ -40,6 +41,9 @@ INSTALLED_APPS = [
'main.apps.MainConfig',
'cart.apps.CartConfig',
'orders.apps.OrdersConfig',
'users.apps.UsersConfig',
]
MIDDLEWARE = [
@ -143,3 +147,42 @@ EMAIL_HOST_USER = 'fhjj3590@gmail.com'
EMAIL_HOST_PASSWORD = 'uzmd nmit yxwc sgox' # Предполагается, что это сгенерированный пароль для приложений
EMAIL_PORT = 587
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.urls import path,include
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
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('', include('main.urls', namespace='main')),
path('auth/', include('users.urls')), # Подключение URL приложения users
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT)
urlpatterns += static(settings.MEDIA_URL, 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',
blank=True)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10,
decimal_places=2)
price = models.IntegerField(max_length=19)
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=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">
<a class="nav-link text-white" href="{% url 'cart:cart_detail' %}">Корзина</a>
</li>
{# <li class="nav-item dropdown ">#}
{# <a class="nav-link dropdown-toggle text-white" href="#" role="button"#}
{# data-bs-toggle="dropdown" aria-expanded="false">#}
{# Мой профиль#}
{# </a>#}
{# <ul class="dropdown-menu">#}
{# #}
{# <li><a class="dropdown-item text-white" href="#">Личный кабинет</a>#}
{# </li>#}
{##}
{# <li>#}
{# <hr class="dropdown-divider">#}
{# </li>#}
{##}
{# </ul>#}
{# </li>#}
<li class="nav-item">
<a class="nav-link text-white" href="{% url 'keycloak_login' %}">Войти</a>
<li><a class="dropdown-item text-white" href="{% url 'profile' %}">Личный кабинет</a></li>
</li>
{% if request.session.keycloak_user_id %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Мой профиль
</a>
<ul class="dropdown-menu">
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-white" href="{% url 'keycloak_logout' %}">Выйти</a></li>
</ul>
</li>
{% endif %}
</ul>
</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):
order = models.ForeignKey(Order, related_name='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)
def __str__(self):

View File

@ -3,6 +3,7 @@ from .models import OrderItem, Order
from .forms import OrderCreateForm
from cart.cart import Cart
from django.conf import settings
from decimal import Decimal
from .utils import send_invoice_via_email
@ -27,4 +28,8 @@ def order_create(request):
def order_created(request, 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')