Дропшипинг-магазин на Django (шпаргалка)

Эта статья не является полноценным руководством, а только содержит отдельные кусочки кода. Тезисно.

Установка Django в виртуальное окружение

Командами ниже мы сначала обновляем менеджер пакетов pip, а потом инсталлируем Джанго

python.exe -m pip install --upgrade pip
pip install django

Установка django-mptt (нужен для того, чтобы можно было создавать древовидные категории), а также Pillow (для работы с изображениями)

pip install django-mptt
pip install Pillow

Создание проекта Django

Существует два распространённых варианта установки. Самый популярный из них - это когда главная папка и папка проекта (папка основных настроек Дажнго) будет иметь одно и тоже название:

django-admin startproject ИМЯ_ПРОЕКТА

Но лично мне удобнее, чтобы папка основных настроек называлась main. В этом случае команда будет выглядеть так (точка после слова main через пробел - обязательна):

django-admin startproject main .

В файле main\settings.py зададим временную зону и русифицируем:

LANGUAGE_CODE = 'ru-RU'
TIME_ZONE = 'Europe/Moscow'

Создадим константу, которая содержит путь к html-шаблонам. Для этого находим в файле константу строку BASE_DIR = Path(__file__).resolve().parent.parent и ниже пишем:

TEMPLATES_DIRS = os.path.join(BASE_DIR, 'templates')

далее укажем константу TEMPLATES_DIRS в 'DIRS':

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATES_DIRS],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Пропишем статику после строки STATIC_URL = 'static/':

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

В корне проекта создадим папки static и templates:

Создание папок static и templates в проекте джанго

Создадим приложение shop

django-admin startapp shop

Проводим первую миграцию

python manage.py makemigrations
python manage.py migrate

Создаём суперпользователя

python manage.py createsuperuser

Стартуем сервер из консоли

python manage.py runserver

А вообще, лучше всего, если в PyCharm создадим конфигурацию запуска на этот случай вот так:

PyCharm создание конфигурации запуска django

Добавим вывод приложения Shop в админку Django

INSTALLED_APPS = [
...
    'mptt',
    'shop.apps.ShopConfig',
]

Модель магазина:

import json

import requests
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse  # для древовидной структуры категорий
from mptt.models import MPTTModel, TreeForeignKey  # для древовидной структуры категорий
from django.core.validators import MinValueValidator  # валидатор значений


# Create your models here.
# ========== Товары ==========
class Product(models.Model):
    title = models.CharField(max_length=255, verbose_name='Название товара')
    slug = models.SlugField(max_length=150, verbose_name='slug', null=False, unique=True)
    description = models.TextField(verbose_name='Описание товара')
    category = TreeForeignKey('ProductCategory', on_delete=models.PROTECT, default=1,
                              related_name='products', verbose_name='Категория товара')

    # meta
    sku = models.CharField(max_length=255, verbose_name='Артикул')
    barcode = models.CharField(verbose_name='Штрихкод', max_length=255, blank=True)
    manufacturer = models.CharField(verbose_name='Компания-производитель', max_length=255, blank=True)
    manufacturer_countries = models.CharField(max_length=255, verbose_name='Страна-производитель', blank=True)
    vendor = models.CharField(max_length=255, verbose_name='Производитель', blank=True)
    vendor_code = models.CharField(max_length=255, verbose_name='Артикул производителя', blank=True)

    # габариты
    length = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Длина (см.)', null=True, blank=True)
    width = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Ширина (см.)', null=True, blank=True)
    height = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Высота (см.)', null=True, blank=True)
    weight = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Вес (г.)', null=True, blank=True)

    # цена и остатки
    price = models.DecimalField(max_digits=20, decimal_places=2, verbose_name='Цена', default=1,
                                validators=[MinValueValidator(0)])
    sale_price = models.DecimalField(max_digits=20, decimal_places=2, verbose_name='Цена со скидкой',
                                     null=True, blank=True, validators=[MinValueValidator(0)])
    quantity = models.IntegerField(verbose_name='Количество товара', default=0, null=True)

    # информация о поставках
    supplier = models.ForeignKey('Supplier', verbose_name='Поставщик',  # в кавычках имя класса
                                 on_delete=models.CASCADE, null=True, blank=True)
    supplier_sku = models.IntegerField(verbose_name='Артикул у поставщика', default=0, null=True)
    supplier_url = models.URLField(verbose_name='URL', blank=True)
    supplier_price = models.IntegerField(verbose_name='Цена у поставщика', default=0, null=True,
                                         validators=[MinValueValidator(0)])
    supplier_quantity = models.IntegerField(verbose_name='Количество товара у поставщика', default=0, null=True)

    # данные синхронизации
    ozon_id = models.PositiveIntegerField(verbose_name='OZON ID', null=True, blank=True)
    wb_id = models.PositiveIntegerField(verbose_name='Wildberries ID', null=True, blank=True)
    yandex_id = models.PositiveIntegerField(verbose_name='Яндекс ID', null=True, blank=True)
    vk_id = models.PositiveIntegerField(verbose_name='VK ID', null=True, blank=True)

    # мета публикации
    publish = models.BooleanField(verbose_name='Опубликовано', default=True)
    author = models.ForeignKey(User, verbose_name='Автор', on_delete=models.CASCADE, default=1)
    created = models.DateTimeField(verbose_name='Дата создания', auto_now_add=True)
    updated = models.DateTimeField(verbose_name='Дата обновления', auto_now=True)

    class Meta:
        ordering = ['pk']
        verbose_name = 'Товар'
        verbose_name_plural = 'Товары'

    def __str__(self):
        return f'{self.title} --- {self.price}'


class ProductImages(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='static/uploads/images/%Y/%m', verbose_name='Изображение')
    alt = models.CharField(max_length=255, verbose_name='Атрибут alt', blank=True)
    order = models.PositiveIntegerField(verbose_name='Порядок сортировки', null=True, default=0, blank=True)
    supplier_url = models.URLField(verbose_name='URL', blank=True)
    vk_id = models.PositiveIntegerField(verbose_name='VK ID', null=True, blank=True)


class ProductCategory(MPTTModel):
    title = models.CharField(max_length=50, unique=True, verbose_name='Название')
    slug = models.SlugField(max_length=150, verbose_name='slug', null=False, unique=True)
    parent = TreeForeignKey('self', on_delete=models.PROTECT, null=True, blank=True, related_name='children',
                            db_index=True, verbose_name='Родительская категория')

    supplier = models.ForeignKey('Supplier', verbose_name='Поставщик',  # в кавычках имя класса
                                 on_delete=models.CASCADE, null=True, blank=True)

    # данные синхронизации
    ozon_id = models.PositiveIntegerField(verbose_name='OZON ID', null=True, blank=True)
    wb_id = models.PositiveIntegerField(verbose_name='Wildberries ID', null=True, blank=True)
    yandex_id = models.PositiveIntegerField(verbose_name='Яндекс ID', null=True, blank=True)
    vk_id = models.PositiveIntegerField(verbose_name='VK ID', null=True, blank=True)

    class MPTTMeta:
        order_insertion_by = ['title']

    class Meta:
        unique_together = [['parent', 'slug']]
        verbose_name = 'Категория'
        verbose_name_plural = 'Категории'

    def get_absolute_url(self):
        return reverse('product-by-category', args=[str(self.slug)])

    def __str__(self):
        return self.title


# ========== Поставщики ==========
class Supplier(models.Model):
    name = models.CharField(max_length=255, verbose_name='Название')
    site = models.URLField(verbose_name='Сайт', blank=True)
    first_name = models.CharField(max_length=50, verbose_name='Имя', blank=True)
    last_name = models.CharField(max_length=50, verbose_name='Фамилия', blank=True)
    email = models.EmailField(verbose_name='Email', blank=True)
    phone = models.CharField(max_length=50, verbose_name='Телефон', blank=True)
    address = models.CharField(max_length=250, verbose_name='Адрес', blank=True)
    comment = models.TextField(verbose_name='Примечание', blank=True)
    markup = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Наценка', null=True, blank=True)

    class Meta:
        verbose_name = 'Поставщик'
        verbose_name_plural = 'Поставщики'

    def __str__(self):
        return f'{self.name}'


# ========== Заказы ==========
class Order(models.Model):
    first_name = models.CharField(verbose_name='Имя', max_length=50)
    last_name = models.CharField(verbose_name='Фамилия', max_length=50)
    email = models.EmailField(verbose_name='Email')
    address = models.CharField(verbose_name='Адрес', max_length=250)
    postal_code = models.CharField(verbose_name='Индекс', max_length=20)
    city = models.CharField(verbose_name='Город', max_length=100)
    comment = models.TextField(verbose_name='Комментарий', blank=True, null=True)
    created = models.DateTimeField(verbose_name='Дата создания', auto_now_add=True)
    updated = models.DateTimeField(verbose_name='Дата обновления', auto_now=True)
    paid = models.BooleanField(verbose_name='Оплачено', default=False)

    STATUS_CART = '1_cart'
    STATUS_WAITING_FOR_PAYMENT = '2_waiting_for_payment'
    STATUS_PAID = '3_paid'

    STATUS_CHOICES = [
        (STATUS_CART, 'cart'),
        (STATUS_WAITING_FOR_PAYMENT, 'waiting_for_payment'),
        (STATUS_PAID, 'paid')
    ]
    status = models.CharField(max_length=32, choices=STATUS_CHOICES, default=STATUS_CART)

    class Meta:
        ordering = ('-created',)
        verbose_name = 'Заказ'
        verbose_name_plural = 'Заказы'

    def __str__(self):
        return 'Order {}'.format(self.id)

    def get_total_cost(self):
        return sum(item.get_cost() for item in self.items.all())


class OrderItem(models.Model):
    order = models.ForeignKey(Order, verbose_name='Заказ', on_delete=models.CASCADE)
    product = models.ForeignKey(Product, verbose_name='Товар', on_delete=models.PROTECT)
    quantity = models.PositiveIntegerField(verbose_name='Количество', default=1)
    price = models.DecimalField(verbose_name='Цена', max_digits=20, decimal_places=2)
    discount = models.DecimalField(verbose_name='Цена со скидкой', max_digits=20, decimal_places=2, default=0)

    class Meta:
        ordering = ['pk']

    def __str__(self):
        return f'{self.product} --- {self.price}'

    def get_cost(self):
        return self.price * self.quantity

admin.py

from django.contrib import admin
from mptt.admin import MPTTModelAdmin  # для древовидной структуры категорий
from shop.models import Product, ProductCategory, ProductImages, Supplier, Order, OrderItem


# Register your models here.
# admin.site.register(Product)

class SupplierAdmin(admin.ModelAdmin):

    class Meta:
        verbose_name = 'Товар'
        verbose_name_plural = 'Товары'


admin.site.register(Supplier, SupplierAdmin)

class ProductImagesAdmin(admin.ModelAdmin):

    class Meta:
        verbose_name = 'Изображение'
        verbose_name_plural = 'Изображения'


admin.site.register(ProductImages, ProductImagesAdmin)

class ProductAdminInline(admin.TabularInline):
    model = ProductImages
    extra = 0


class ProductCategoryAdmin(MPTTModelAdmin):
    prepopulated_fields = {'slug': ('title',)}


admin.site.register(ProductCategory, ProductCategoryAdmin)


class ProductAdmin(admin.ModelAdmin):
    list_display = ['title', 'price', 'sale_price', 'quantity', 'publish', 'created', 'updated']
    list_filter = ['publish', 'created']
    list_editable = ['quantity', 'publish']
    prepopulated_fields = {'slug': ('title',)}

    inlines = [ProductAdminInline]
    """ https://russianblogs.com/article/6525692725/
    Атрибут prepopulated_fields используется для указания полей, которые используют значения других полей для 
    автоматической генерации значений. Как мы видели ранее, это простой способ генерировать слагов. Используйте 
    свойство list_editable в классе ProductAdmin, чтобы задать поля, которые можно изменить на странице отображения 
    списка на веб-сайте администратора. Таким образом, вы можете редактировать несколько строк одновременно.
    Так как редактируется только отображаемый контент, любое поле list_editable должно быть в list_display.
    """

    class Meta:
        verbose_name = 'Товар'
        verbose_name_plural = 'Товары'


admin.site.register(Product, ProductAdmin)


# Register your models here.
class OrderItemInline(admin.TabularInline):
    model = OrderItem
    raw_id_fields = ['product']


class OrderAdmin(admin.ModelAdmin):
    list_display = ['id', 'first_name', 'last_name', 'email', 'address',
                    'postal_code', 'city', 'paid', 'created', 'updated']
    list_filter = ['paid', 'created', 'updated']
    inlines = [OrderItemInline]


admin.site.register(Order, OrderAdmin)
Рейтинг: 5

2023-10-24 / / 0 комментариев / Про кодинг и сервер / ,