Compare commits
6 Commits
legacy-201
...
rework-202
| Author | SHA1 | Date | |
|---|---|---|---|
| be4839ecc2 | |||
| b2b38783cb | |||
| f0502e605f | |||
| 75d359aeb3 | |||
| 5b6c8a8ded | |||
| 5cb05a353f |
27
.env.example
27
.env.example
@@ -1,3 +1,27 @@
|
||||
# Локальная конфигурация для dev-окружения lpon-site.
|
||||
DJANGO_DEBUG=True
|
||||
DJANGO_SECRET_KEY=insecure-dev-key-change-me-in-prod
|
||||
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver,lpon.ru
|
||||
DJANGO_ADMINS=YouName:you@email.com
|
||||
|
||||
# Доверенные источники для CSRF (важно для Docker/Nginx)
|
||||
# Укажите здесь URL, по которому вы заходите на сайт (с протоколом и портом gunicorn)
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS=http://127.0.0.1:8000,http://localhost:8000,http://testserver,https://lpon.ru
|
||||
|
||||
# URL для доступа к админке Django (можно сменить для безопасности, чтобы боты не могли её найти)
|
||||
DJANGO_ADMIN_URL=admin/
|
||||
|
||||
# База данных (SQLite)
|
||||
DJANGO_SQLITE_NAME=lpon-db.sqlite3
|
||||
|
||||
# Почта SMTP
|
||||
DJANGO_EMAIL_HOST=smtp.mail.ru
|
||||
DJANGO_EMAIL_PORT=2525
|
||||
DJANGO_EMAIL_HOST_USER=you@email.com
|
||||
DJANGO_EMAIL_HOST_PASSWORD=CHANGE_ME
|
||||
DJANGO_EMAIL_FROM=you@email.com
|
||||
|
||||
# ==============================================
|
||||
# Системные пути на хосте (ТОЛЬКО ДЛЯ ПРОДАКШЕНА)
|
||||
# Используется скриптом для генерации корректного Nginx конфига и alias к media-файлам.
|
||||
# На локальной машине разработчика (Dev) эта переменная игнорируется.
|
||||
@@ -5,6 +29,7 @@
|
||||
HOST_PROJECT_PATH=/home/user/docker-app/your-site
|
||||
|
||||
|
||||
# Настройки достпа к пакетам в репозитории, чтобы wathtower мог проверять их свежесть и скачивать
|
||||
# ==============================================
|
||||
# Настройки достпа к пакетам в репозитории, чтобы watchtower мог проверять их свежесть и скачивать
|
||||
REPO_USER=xxxxx
|
||||
REPO_PASS=xxxxx
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -363,7 +363,8 @@ db.sqlite3-wal
|
||||
public/media/*
|
||||
!public/media/README.md
|
||||
staticfiles/
|
||||
public/static/static_collected/
|
||||
public/static_collected/
|
||||
.github/
|
||||
|
||||
# Data Backup
|
||||
database/data.json
|
||||
database/
|
||||
|
||||
0
lpon_site/frontend/__init__.py
Normal file
0
lpon_site/frontend/__init__.py
Normal file
3
lpon_site/frontend/admin.py
Normal file
3
lpon_site/frontend/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
lpon_site/frontend/apps.py
Normal file
5
lpon_site/frontend/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FrontendConfig(AppConfig):
|
||||
name = 'frontend'
|
||||
0
lpon_site/frontend/migrations/__init__.py
Normal file
0
lpon_site/frontend/migrations/__init__.py
Normal file
380
lpon_site/frontend/models.py
Normal file
380
lpon_site/frontend/models.py
Normal file
@@ -0,0 +1,380 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
class TbArtist(models.Model):
|
||||
s_artist = models.CharField(
|
||||
max_length=255,
|
||||
db_index=True,
|
||||
verbose_name='Исполнитель',
|
||||
help_text="Исполнитель или группа.",
|
||||
)
|
||||
s_artist_html = models.TextField(
|
||||
blank=True, null=True, default='',
|
||||
verbose_name='Исполнитель (типографированно в HTML)',
|
||||
help_text='Исполнитель или группа, указанные на обложке релиза, с сохранением типографирования и спецсимволов в виде HTML-тегов.',
|
||||
)
|
||||
s_slug = models.SlugField(
|
||||
max_length=64,
|
||||
)
|
||||
j_artist_in_source = models.JSONField(
|
||||
default=list, blank=True, null=True,
|
||||
verbose_name='Исполнитель в источнике',
|
||||
help_text='Список вариантов написания исполнителя, встречающихся в источниках (в разных релизах/каталогах'
|
||||
' или у разных поставщиков). Например: <tt>["The Beatles", "Beatles", "Beatles, The"]</tt>.',
|
||||
)
|
||||
t_created = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name="Дата Создания записи в БД",
|
||||
)
|
||||
t_updated = models.DateTimeField(
|
||||
auto_now=True,
|
||||
verbose_name="Дата последнего обновления записи в БД",
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return f"product {self.id:0>4}: {self.s_artist}"
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Исполнитель'
|
||||
verbose_name_plural = 'Исполнители'
|
||||
ordering = ('s_artist',)
|
||||
|
||||
|
||||
class TbProduct(models.Model):
|
||||
# Абстрактный релиз / сущность / товар, который может быть представлен в виде одного или нескольких предложений
|
||||
# от разных продавцов.
|
||||
k_artists = models.ManyToManyField(
|
||||
TbArtist,
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
related_name='+', # ← '+' отключает обратную связь
|
||||
verbose_name='Исполнители',
|
||||
help_text="Выберите исполнителей или группы для этого релиза (можно не указывать)",
|
||||
)
|
||||
s_title = models.CharField(
|
||||
max_length=255, blank=False, null=False, default='',
|
||||
# db_index=True,
|
||||
verbose_name='Название товара (релиза)',
|
||||
help_text='Название товара (релиза), как указано на обложке. Например: <tt>Abbey Road (remastered)</tt>'
|
||||
' или <tt>TDK, CDing I, 90</tt>.',
|
||||
)
|
||||
s_title_html = models.TextField(
|
||||
blank=True, null=True, default='',
|
||||
verbose_name='Название товара (типографированно в HTML)',
|
||||
help_text='Название товара (релиза), как указано на обложке, с сохранением типографирования и спецсимволов'
|
||||
' в виде HTML-тегов.',
|
||||
)
|
||||
s_title_slug = models.SlugField(
|
||||
blank=True, null=True, default='',
|
||||
verbose_name='Слаг',
|
||||
help_text='Слаг для товара, формируемый на основе названия товара (релиза)',
|
||||
)
|
||||
t_title_date = models.DateField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name='Дата релиза',
|
||||
help_text='Дата релиза, если известна. Если известен только год, можно указать 1 января этого года.'
|
||||
' Например: <tt>1969-09-26</tt> или не указывать вовсе.',
|
||||
)
|
||||
i_discogs_master_id = models.IntegerField(
|
||||
blank=True, null=True, default=None,
|
||||
verbose_name='ID на мастер-релиз Discogs',
|
||||
help_text='Уникальный идентификатор мастер-релиза на Discogs, если он там есть. Например: <tt>306323</tt>',
|
||||
)
|
||||
j_product_metadata = models.JSONField(
|
||||
default=dict, blank=True, null=True,
|
||||
verbose_name='Дополнительные данные',
|
||||
help_text='Дополнительные данные о релизе в виде JSON-словаря,',
|
||||
)
|
||||
t_created = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name="Дата Создания записи в БД",
|
||||
)
|
||||
t_updated = models.DateTimeField(
|
||||
auto_now=True,
|
||||
verbose_name="Дата последнего обновления записи в БД",
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return f"product {self.id:0>4}: {self.s_title}"
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Релиз (товар)'
|
||||
verbose_name_plural = 'Релизы (товары)'
|
||||
ordering = ('s_title',)
|
||||
|
||||
|
||||
class TbSeller(models.Model):
|
||||
# Продавец товара
|
||||
class SellerType(models.TextChoices):
|
||||
SELLER = 'seller', 'Продавец'
|
||||
LABEL = 'label', 'Лейбл (издатель)'
|
||||
DIY = 'diy', 'Самиздат группы'
|
||||
CROWD = 'crowdfunding', 'Краудфандинг'
|
||||
OTHER = '++', 'Другое'
|
||||
|
||||
s_seller = models.CharField(
|
||||
max_length=32, blank=False, null=False, default='',
|
||||
verbose_name='Название продавца',
|
||||
help_text='Название продавца или магазина, например: <tt>Клюква Рекодс</tt>',
|
||||
)
|
||||
s_seller_slug = models.SlugField(
|
||||
max_length=32, blank=False, null=False, default='',
|
||||
verbose_name='Слаг',
|
||||
help_text='Слаг для продавца, формируемый на основе названия продавца',
|
||||
)
|
||||
l_seller_type = models.CharField(
|
||||
max_length=9, default=SellerType.SELLER, choices=SellerType.choices,
|
||||
verbose_name='Тип продавца',
|
||||
)
|
||||
j_seller_metadata = models.JSONField(
|
||||
default=dict, blank=True, null=True,
|
||||
verbose_name='Дополнительные данные',
|
||||
help_text='Дополнительные данные о продавце в виде JSON-словаря',
|
||||
)
|
||||
|
||||
def __unicode__(self):
|
||||
return f"vendor {self.id:0>3}: {self.s_vendor}"
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Продавец'
|
||||
verbose_name_plural = 'Продавцы'
|
||||
ordering = ('s_seller',)
|
||||
|
||||
class TbLabel(models.Model):
|
||||
# Лейблы: производители (Улитка Рекородс, Мелодия, Sony... а так же производители TDK, AXIA, Maxwell и т.д.)
|
||||
s_label = models.CharField(
|
||||
max_length=32, blank=False, null=False, default='',
|
||||
verbose_name='Название лейбла',
|
||||
help_text='Название лейбла, например: <tt>Мелодия</tt> или <tt>TDK</tt>',
|
||||
)
|
||||
s_label_slug = models.SlugField(
|
||||
max_length=32, blank=False, null=False, default='',
|
||||
verbose_name='Слаг',
|
||||
help_text='Слаг для лейбла, формируемый на основе названия лейбла',
|
||||
)
|
||||
j_label_metadata = models.JSONField(
|
||||
default=dict, blank=True, null=True,
|
||||
verbose_name='Дополнительные данные',
|
||||
help_text='Дополнительные данные о лейбле в виде JSON-словаря',
|
||||
)
|
||||
|
||||
|
||||
#
|
||||
class TbOffer(models.Model):
|
||||
# Конкретное предложение продавца
|
||||
class Format(models.TextChoices):
|
||||
LP = 'lp', 'vinyl'
|
||||
CD = 'cd', 'CD'
|
||||
BD = 'bd', 'Blu-ray'
|
||||
CS = 'cs', 'Cassette'
|
||||
MD = 'md', 'Minidisc'
|
||||
BX = 'bx', 'Box Set'
|
||||
HI = 'hi', 'Hi-Fi'
|
||||
AC = 'ac', 'Accessory'
|
||||
OTHER = '++', 'Other'
|
||||
|
||||
class Condition(models.TextChoices):
|
||||
S = 's', 'Still Sealed (новое, запечатано)'
|
||||
M = 'm', 'Mint'
|
||||
NM = 'nm', 'Near Mint'
|
||||
VG = 'vg', 'Very Good'
|
||||
G = 'g', 'Good'
|
||||
F = 'f', 'Fair'
|
||||
P = 'p', 'Poor'
|
||||
OTHER = '++', 'Other'
|
||||
|
||||
class Currency(models.TextChoices):
|
||||
RUB = 'rub', 'RUB: российский рубль'
|
||||
TRY = 'try', 'TRY: турецкая лира'
|
||||
AMD = 'amd', 'AMD: армянский драм'
|
||||
USD = 'usd', 'USD: американский доллар'
|
||||
EUR = 'eur', 'EUR: евро'
|
||||
JPY = 'jpy', 'JPY: японская иена'
|
||||
GBP = 'gbp', 'GBP: британский фунт'
|
||||
CNY = 'cny', 'CNY: китайский юань'
|
||||
BYN = 'byn', 'BYN: белорусский рубль'
|
||||
TON = 'ton', 'TON: криптовалюта TON'
|
||||
OTHER = '++', 'Other'
|
||||
|
||||
s_catalog_num = models.TextField(
|
||||
blank=True, default='',
|
||||
verbose_name='Каталожный номер или barcode',
|
||||
help_text='Каталожный номер релиза, если он есть. Например: <tt>SD 16023</tt> для <i>ABBA — Super Trouper'
|
||||
' — 1980 — Atlantic (USA)</i>',
|
||||
)
|
||||
k_product = models.ForeignKey(
|
||||
TbProduct,
|
||||
blank=True, default=None,
|
||||
on_delete=models.SET_NULL, related_name='+',
|
||||
verbose_name='Релиз (товар)',
|
||||
)
|
||||
k_label = models.ForeignKey(
|
||||
TbLabel,
|
||||
null=True, default=None,
|
||||
on_delete=models.SET_NULL, related_name='+',
|
||||
verbose_name='Лейбл',
|
||||
help_text='Лейбл, на котором был выпущен релиз, если он известен. Например: <tt>Atlantic</tt> или <tt>Мелодия</tt>',
|
||||
)
|
||||
k_seller = models.ForeignKey(
|
||||
TbSeller,
|
||||
null=True, default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
verbose_name='Продавец',
|
||||
)
|
||||
l_primary_media = models.CharField(
|
||||
max_length=2,
|
||||
choices=Format.choices,
|
||||
default=Format.LP,
|
||||
verbose_name='Формат',
|
||||
help_text='Основной формат носителя (пластинка, CD, кассета и т.п.)'
|
||||
)
|
||||
i_discogs_id = models.IntegerField(
|
||||
blank=True,default=0,
|
||||
verbose_name='ID на релиз Discogs',
|
||||
help_text='Уникальный идентификатор релиза на Discogs, если он там есть. Например: <tt>306323</tt>',
|
||||
)
|
||||
l_condition_media = models.CharField(
|
||||
max_length=2,
|
||||
choices=Condition.choices,
|
||||
default=Condition.S,
|
||||
verbose_name="Состояние носителя",
|
||||
help_text='Состояние носителя (пластинки, CD и т.п.) по шкале от "Still Sealed" (запечатано) до "Poor" (плохое).',
|
||||
)
|
||||
l_condition_sleeve = models.CharField(
|
||||
max_length=2,
|
||||
choices=Condition.choices,
|
||||
default=Condition.S,
|
||||
verbose_name="Состояние обложки",
|
||||
help_text='Состояние обложки по шкале от "Still Sealed" (запечатано) до "Poor" (плохое).',
|
||||
)
|
||||
f_price = models.DecimalField(
|
||||
max_digits=10,
|
||||
decimal_places=2,
|
||||
blank=True, default=0.00,
|
||||
verbose_name='Цена',
|
||||
)
|
||||
l_currency = models.CharField(
|
||||
max_length=3,
|
||||
choices=Currency.choices,
|
||||
default=Currency.RUB,
|
||||
verbose_name="Валюта",
|
||||
)
|
||||
i_quantity = models.IntegerField(
|
||||
blank=True, default=0,
|
||||
verbose_name='Количество',
|
||||
)
|
||||
b_is_available = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name='В наличии',
|
||||
)
|
||||
i_discount_to_daily_sale = models.IntegerField(
|
||||
blank=True, default=0,
|
||||
verbose_name='Скидка',
|
||||
help_text='Процент скидки, для ежедневной распродажи',
|
||||
)
|
||||
s_offer_comment = models.TextField(
|
||||
blank=True, default='',
|
||||
verbose_name='Доп.инфо',
|
||||
help_text='Дополнительная информация или комментарий к предложению от продавца, например:'
|
||||
' <tt>"Пластинка запаяна в целлофан, угол обложки замят."</tt>.',
|
||||
)
|
||||
j_offer_metadata = models.JSONField(
|
||||
default=dict, null=True,
|
||||
verbose_name='Дополнительные данные',
|
||||
help_text='Дополнительные данные о предложении в виде JSON-словаря',
|
||||
)
|
||||
t_created = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name="Дата Создания записи в БД",
|
||||
)
|
||||
t_updated = models.DateTimeField(
|
||||
auto_now=True,
|
||||
verbose_name="Дата последнего обновления записи в БД",
|
||||
)
|
||||
|
||||
#
|
||||
# │ 1
|
||||
# │
|
||||
# │ N
|
||||
# ┌───────▼────────────┐
|
||||
# │ ProductImage │
|
||||
# ├────────────────────┤
|
||||
# │ id │
|
||||
# │ product_id │ FK
|
||||
# │ image_type │ ← cover/back/obi/matrix/etc
|
||||
# │ source │ ← discogs/manual/vendor
|
||||
# │ url │
|
||||
# │ local_path │
|
||||
# │ is_primary │
|
||||
# │ sort_order │
|
||||
# │ metadata_json │
|
||||
# └────────────────────┘
|
||||
#
|
||||
#
|
||||
# ┌────────────────────┐
|
||||
# │ ProductEnrichment │
|
||||
# ├────────────────────┤
|
||||
# │ id │
|
||||
# │ product_id │ FK
|
||||
# │ source │ ← discogs/api/scraper
|
||||
# │ confidence_score │
|
||||
# │ data_json │
|
||||
# │ fetched_at │
|
||||
# └────────────────────┘
|
||||
#
|
||||
#
|
||||
# ┌────────────────────┐
|
||||
# │ PriceHistory │
|
||||
# ├────────────────────┤
|
||||
# │ id │
|
||||
# │ offer_id │ FK
|
||||
# │ old_price │
|
||||
# │ new_price │
|
||||
# │ quantity_snapshot │
|
||||
# │ source │
|
||||
# │ changed_at │
|
||||
# └────────────────────┘
|
||||
#
|
||||
#
|
||||
# ┌────────────────────┐
|
||||
# │ Source │ ← Импорт / источник
|
||||
# ├────────────────────┤
|
||||
# │ id │
|
||||
# │ vendor_id │ FK
|
||||
# │ type │ ← excel/csv/api/html
|
||||
# │ source_url │
|
||||
# │ file_name │
|
||||
# │ file_hash │
|
||||
# │ imported_at │
|
||||
# │ parser_version │
|
||||
# │ meta_json │
|
||||
# └─────────┬──────────┘
|
||||
# │ 1
|
||||
# │
|
||||
# │ N
|
||||
# ┌─────────▼──────────┐
|
||||
# │ SourceItem │ ← Строка из Excel/CSV
|
||||
# ├────────────────────┤
|
||||
# │ id │
|
||||
# │ source_id │ FK
|
||||
# │ row_index │
|
||||
# │ raw_row_json │
|
||||
# │ parsed_data_json │
|
||||
# │ matching_status │
|
||||
# │ confidence_score │
|
||||
# │ matched_product_id │ FK nullable
|
||||
# │ created_offer_id │ FK nullable
|
||||
# │ created_at │
|
||||
# └────────────────────┘
|
||||
3
lpon_site/frontend/tests.py
Normal file
3
lpon_site/frontend/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
lpon_site/frontend/views.py
Normal file
3
lpon_site/frontend/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
lpon_site/lpon_site/__init__.py
Normal file
0
lpon_site/lpon_site/__init__.py
Normal file
16
lpon_site/lpon_site/asgi.py
Normal file
16
lpon_site/lpon_site/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for lpon_site project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lpon_site.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
149
lpon_site/lpon_site/settings.py
Normal file
149
lpon_site/lpon_site/settings.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Django settings for lpon_site project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 6.0.5.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/6.0/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import environ
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Читаем переменные окружения
|
||||
env = environ.Env()
|
||||
environ.Env.read_env(os.path.join(BASE_DIR.parent, '.env'))
|
||||
|
||||
def _normalize_admin_url(value: str) -> str:
|
||||
"""Приводит URL админки к виду `segment/` без ведущего слэша."""
|
||||
normalized = value.strip().lstrip('/')
|
||||
if not normalized:
|
||||
return 'admin/'
|
||||
if not normalized.endswith('/'):
|
||||
normalized += '/'
|
||||
return normalized
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = env('DJANGO_SECRET_KEY', default='3xym$l+!)erah-k23lf0=t=c_4$e0nr*zls&l%pbz@k6v6qn89')
|
||||
ADMIN_URL = _normalize_admin_url(env('DJANGO_ADMIN_URL', default='admin/'))
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = env.bool('DJANGO_DEBUG', default=False)
|
||||
|
||||
ALLOWED_HOSTS = env.list(
|
||||
'DJANGO_ALLOWED_HOSTS',
|
||||
default=['127.0.0.1', 'localhost', 'testserver', 'lpon.ru'],
|
||||
)
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = env.list('DJANGO_CSRF_TRUSTED_ORIGINS', default=['127.0.0.1', 'localhost', 'testserver'])
|
||||
|
||||
#########################################
|
||||
# Настройки сообщений об ошибках когда все упало и т.п.
|
||||
ADMINS = tuple(
|
||||
tuple(item.split(':', 1))
|
||||
for item in env.list('DJANGO_ADMINS', default=['S.Erjemin:erjemin@gmail.com'])
|
||||
)
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'lpon_site.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'lpon_site.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR.parent.joinpath('database', env('DJANGO_SQLITE_NAME', default='lpon-db.sqlite3')),
|
||||
'OPTIONS': {
|
||||
'timeout': 25,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
||||
LANGUAGE_CODE = 'ru-RU' #
|
||||
# TIME_ZONE = 'Etc/GMT+3' #
|
||||
TIME_ZONE = 'Europe/Moscow' #
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
FIRST_DAY_OF_WEEK = 1 # неделя начинается с понедельника
|
||||
DEFAULT_CHARSET = 'utf-8'
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/6.0/howto/static-files/
|
||||
STATIC_URL = '/static/'
|
||||
MEDIA_URL = '/media/'
|
||||
# Локальные каталоги проекта: медиа и статика лежат рядом в `public`.
|
||||
PUBLIC_DIR = BASE_DIR.parent.joinpath('public') # Папка `public` находится в корне пректа
|
||||
MEDIA_ROOT = PUBLIC_DIR.joinpath('media')
|
||||
STATICFILES_DIRS = [PUBLIC_DIR.joinpath('static')]
|
||||
STATIC_ROOT = PUBLIC_DIR.joinpath('staticfiles')
|
||||
23
lpon_site/lpon_site/urls.py
Normal file
23
lpon_site/lpon_site/urls.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
URL configuration for lpon_site project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/6.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
|
||||
from lpon_site import settings
|
||||
|
||||
urlpatterns = [
|
||||
path(settings.ADMIN_URL, admin.site.urls),
|
||||
]
|
||||
16
lpon_site/lpon_site/wsgi.py
Normal file
16
lpon_site/lpon_site/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for lpon_site project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lpon_site.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
22
lpon_site/manage.py
Executable file
22
lpon_site/manage.py
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lpon_site.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
110
poetry.lock
generated
Normal file
110
poetry.lock
generated
Normal file
@@ -0,0 +1,110 @@
|
||||
# This file is automatically @generated by Poetry 1.8.0 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "asgiref"
|
||||
version = "3.11.1"
|
||||
description = "ASGI specs, helper code, and adapters"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "asgiref-3.11.1-py3-none-any.whl", hash = "sha256:e8667a091e69529631969fd45dc268fa79b99c92c5fcdda727757e52146ec133"},
|
||||
{file = "asgiref-3.11.1.tar.gz", hash = "sha256:5f184dc43b7e763efe848065441eac62229c9f7b0475f41f80e207a114eda4ce"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["mypy (>=1.14.0)", "pytest", "pytest-asyncio"]
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "6.0.5"
|
||||
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
|
||||
optional = false
|
||||
python-versions = ">=3.12"
|
||||
files = [
|
||||
{file = "django-6.0.5-py3-none-any.whl", hash = "sha256:9d58a7cb49244e74c8e161d5e403a46d6209f1009ba40f5a66d6aa0d0786a8f0"},
|
||||
{file = "django-6.0.5.tar.gz", hash = "sha256:bc6d6872e98a2864c836e42edd644b362db311147dd5aa8d5b82ba7a032f5269"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
asgiref = ">=3.9.1"
|
||||
sqlparse = ">=0.5.0"
|
||||
tzdata = {version = "*", markers = "sys_platform == \"win32\""}
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=23.1.0)"]
|
||||
bcrypt = ["bcrypt (>=4.1.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "django-environ"
|
||||
version = "0.13.0"
|
||||
description = "A package that allows you to utilize 12factor inspired environment variables to configure your Django application."
|
||||
optional = false
|
||||
python-versions = "<4,>=3.9"
|
||||
files = [
|
||||
{file = "django_environ-0.13.0-py3-none-any.whl", hash = "sha256:37799d14cd78222c6fd8298e48bfe17965ff8e586091ad66a463e52e0e7b799e"},
|
||||
{file = "django_environ-0.13.0.tar.gz", hash = "sha256:6c401e4c219442c2c4588c2116d5292b5484a6f69163ed09cd41f3943bfb645f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
develop = ["coverage[toml] (>=5.0a4)", "furo (>=2024.8.6)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)", "sphinx (>=5.0)", "sphinx-copybutton", "sphinx-notfound-page"]
|
||||
docs = ["furo (>=2024.8.6)", "sphinx (>=5.0)", "sphinx-copybutton", "sphinx-notfound-page"]
|
||||
testing = ["coverage[toml] (>=5.0a4)", "pytest (>=4.6.11)", "setuptools (>=71.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "django-extensions"
|
||||
version = "3.2.3"
|
||||
description = "Extensions for Django"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "django-extensions-3.2.3.tar.gz", hash = "sha256:44d27919d04e23b3f40231c4ab7af4e61ce832ef46d610cc650d53e68328410a"},
|
||||
{file = "django_extensions-3.2.3-py3-none-any.whl", hash = "sha256:9600b7562f79a92cbf1fde6403c04fee314608fefbb595502e34383ae8203401"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Django = ">=3.2"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.2"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a"},
|
||||
{file = "python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.5.5"
|
||||
description = "A non-validating SQL parser."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba"},
|
||||
{file = "sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["build"]
|
||||
doc = ["sphinx"]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2026.2"
|
||||
description = "Provider of IANA time zone data"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
files = [
|
||||
{file = "tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7"},
|
||||
{file = "tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10"},
|
||||
]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "7424447119e980f55d2bf47e9e97063dfbb9ae3230680cade5893701992c3e96"
|
||||
22
pyproject.toml
Normal file
22
pyproject.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[tool.poetry]
|
||||
name = "lpon-site"
|
||||
version = "0.1.0"
|
||||
description = "LPON - platform for selling vinyl records (and related products)"
|
||||
authors = ["Sergei Erjemin <erjemin@gmail.com>"]
|
||||
readme = "README.md"
|
||||
packages = [
|
||||
{ include = "lpon_site" }
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.12"
|
||||
django = "^6.0"
|
||||
python-dotenv = "^1.0.0"
|
||||
django-environ = "^0.13.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
django-extensions = "^3.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
Reference in New Issue
Block a user