click fraud detection
click fraud detection
Blog Case

Создание IM штор. Часть 2. Приложение Django

BLOG
CASE
134
0
0/ 5stars
0/5
Время чтения: 20 минут

В прошлой статье мы начали разработку интернет-магазина и загрузили с сайта-примера каталог с сохранением их структуры в файловой системе.  Также мы загрузили список товаров для каждой категории. Нам осталось зайти на страницу товарной позиции, скопировать информацию о товаре и загрузить его изображение.

Взглянем на то, как выглядит HTML-код позиции товара на странице категории:

https://pangardin.com.ua/category/complect/artdeco/

После получения элемента li:

products = soup.find('ul',{'class': 'products'}).findAll('li')

мы выбрали блок с классом inner_product_header и использовали заголовок для нахождения названия товара.

title = product.find('div',{'class': 'inner_product_header'}).find('h3').text

Теперь необходимо получить url-адрес страницы с описанием товара.

link = product.find('div',{"class": "inner_cart_button"}).find('a').get('href')

В этой строке мы вначале находим элемент div с классом inner_cart_button, а потом в нем ссылку, из которой атрибут href.

Загрузив страницу описания товара найдем элемент с изображением в исходном коде.

Получить на него ссылку можно такой строчкой кода, где мы ищем по классу MagicZoomPlus.

img_link = soup.find('a',{"class": "MagicZoomPlus"}).get('href')

Осталось загрузить изображение. 

Создадим новую функцию save_position, сохраняющую позицию товара,  в которую передадим два параметра url и путь к каталогу товара. В этой функции мы получаем html-страницу, находим в ней ссылку на изображение и скачиваем его. Потом создаем папку images, в которую сохраняем изображение на диск.

def save_position(url,path):
    ''' сохранение позиции товара  '''
    r = requests.get(url)
    soup = BeautifulSoup(r.text, 'html.parser')
    img_link = soup.find('a',{"class": "MagicZoomPlus"}).get('href')
    print("Downloading pic %s" % img_link)
    # забираем картинку
    r = requests.get(img_link, stream=True)
    # создаем каталог images
    path_to_img = os.path.join(path,'images')
    if not os.path.isdir(path_to_img):
        os.mkdir(path_to_img)
    # сохраняем картинку
    image_full_path = os.path.join(path_to_img,'1.png')  
    if r.status_code == 200:
        with open(image_full_path, 'wb') as f:
            r.raw.decode_content = True
            shutil.copyfileobj(r.raw, f)

Найдем заголовок товара и описание, сохранив эти данные в файле meta.yml

# находим название и описание
    title = soup.find('h1',{"class": "product_title"}).text  
    description = soup.find('div',{"itemprop": "description"}).text 
    meta_info = '''
name_slug: %s
name_ru: %s
meta_title_ru: %s
meta_keywords_ru: %s
meta_description_ru: %s
is_published: true
description_ru: |
%s
    ''' % (slugify(title),title,title,title,title,description)
    with open(os.path.join(path,'meta.yml'),'w') as f:
        f.write(meta_info)

Для того, чтобы получить более подробную и «чистую» информацию о товаре, понадобиться некоторая сноровка в работе со структурой HTML страницы. Скорее всего, надо будет отсеивать лишнюю информацию и выбирать то что вам нужно для будущего сайта. Пример нашего сайта-источника написан на wordpress и для каждого нового сайта понадобится написать свой собственный парсер контента.

Создание проекта Django и модели приложения

Создадим каталог виртуального окружения, где будем хранить наши зависимости, включая Django.

virtualenv -p python3 venv

Теперь после активации командой: 

. ./venv/bin/activate

можно установить фреймворк Django.

pip install django

Создаем проект, меняем директорию и запускаем миграцию.

django-admin.py startproject prj
cd prj
./manage.py migrate

В процессе миграции будет создана база данных в новом файле db.sqlite3, которая содержит таблицы встроенных в набор поставки приложений Django.

Далее создадим наше приложение:

./manage.py startapp shop

При этом создается директория приложения с типовой структурой файлов.

models.py – для описания классов модели (таблиц базы данных);

admin.py – для классов админ-интерфейса;

views.py – в этом файле мы создаем представления (функции или классы отвечающие за генерацию веб-страниц). Упрощенно можно считать их контроллерами;

tests.py – код с авто-тестами;

apps.py – настройки приложения.

Далее необходимо включить наше приложение в настройках проекта в файле settings.py, добавив его название в переменную списка INSTALLED_APPS.

INSTALLED_APPS = [
        'django.contrib.admin',
        ….
        'django.contrib.staticfiles',
        'shop'
    ]

После чего опишем нашу модель в файле models.py, где определим структуру будущих таблиц базы данных и обозначим их взаимосвязи при помощи внешних ключей.

 from django.db import models

    class Category(models.Model):
        name = models.CharField(max_length=250)
        name_slug = models.CharField(max_length=250)

    class Subcategory(models.Model):
        name = models.CharField(max_length=250)
        name_slug = models.CharField(max_length=250)
        category = models.ForeignKey(Category, on_delete=models.SET_NULL,  null=True)

    class Good(models.Model):
        name = models.CharField(max_length=250)
        name_slug = models.CharField(max_length=250)
        desc = models.TextField()
        subcategory = models.ForeignKey(Subcategory, on_delete=models.SET_NULL,  null=True)

    class Image(models.Model):
        good = models.ForeignKey(Good, on_delete=models.SET_NULL, null=True)
        image = models.ImageField()

Каждый класс модели наследуется от класса models.Model и представляет таблицу. Атрибуты классов - это поля таблиц с определенным типом данных, значением по умолчанию, размерностью и т.д. Связи по внешним ключам определяются полями соответствующего типа ForeignKey и привязываются к классам соответствующих таблиц с указанием логики поведения при удалении связанной записи параметром on_delete.

Админ-интерфейс

Опишем классы админ-интерфейса и привяжем их к классам модели в файле shop/admin.py.

 from django.contrib import admin
    from .models import *

    class CategoryAdmin(admin.ModelAdmin):
        pass

    class SubcategoryAdmin(admin.ModelAdmin):
        pass

    class GoodAdmin(admin.ModelAdmin):
        pass

    class ImageAdmin(admin.ModelAdmin):
        pass

    admin.site.register(Category, CategoryAdmin)
    admin.site.register(Subcategory,SubcategoryAdmin)
    admin.site.register(Good, GoodAdmin)
    admin.site.register(Image, ImageAdmin)

Мы определили пустые классы админок для всех таблиц. Они позволят вывести 4 админ-раздела с настройками по умолчанию.

Однако, ничего не мешает кастомизировать интерфейс, определив специальные атрибуты. 

Например, перечислить поля для поиска в списке можно так:

search_fields = ['name', 'journal__name_ru']

Причем через двойное подчеркивание можно указывать поля для поиска из зависимых таблиц. После определения этого атрибута в админ-панели появиться поле для ввода поискового слова.

Сортировка.

ordering = ['name']

Фильтр.

list_filter = ('is_public', 'company')

Список полей в общем списке записей.

list_display = ('name', 'is_public', 'company')

Исключение ненужных полей из формы редактирования.

exclude = ['uuid_key']

Определение полей в режиме только для чтения.

readonly_fields = ('reader_url', 'moderator' )

C помощью атрибута inlines можно задать класс зависимой таблицы для вывода записей, соответствующих текущему элементу при редактировании  под формой.

inlines = [IssuePageInline, ]

Чтобы вывести картинки в админ-интерфейсе, определим переменную list_display, указав поле image_tag, которое позже создадим в модели.

class ImageAdmin(admin.ModelAdmin):
    list_display = ['image', 'image_tag', 'good']

Добавим новый метод image_tag в класс модели изображений, задекорировав его декоратором @property для того, чтобы иметь возможность ссылаться на метод как на обычное свойство при настройке админ-интерфейса.

class Image(models.Model):
    ''' Изображения  '''
    good = models.ForeignKey(Good, on_delete=models.SET_NULL, null=True)
    image = models.ImageField()

    @property
    def image_tag(self):
        return mark_safe('' % self.image.url)

Функцией mark_safe мы отключаем экранирование html-тегов.

Команда импорта данных.

Создадим новую команду импорта данных (shop/management/commands/import.py).

    from django.core.management.base import BaseCommand, CommandError
    from shop.models import *

    class Command(BaseCommand):
        def handle(self, *args, **options):
            print('Importing data')

Не забываем создавать файлы __ini__.py в новых каталогах management и commands.

Заполним метод handle, где прочитаем каталог с данными и сохраним информацию о каталогах и товарных позициях из файловой системы.

Код импорта разбит на две функции: import_goods и import_catalog, которые импортируют товары и категории соответственно.

Полный код команды и результат выполнения приведен ниже:

from django.core.management.base import BaseCommand, CommandError
from shop.models import *
from prj.settings import BASE_DIR
import os
import sys
import yaml
from django.core.files import File
DATA_DIR = os.path.join(BASE_DIR,'..','data','pangardin.com.ua')

def import_catalog():
    # очищаем таблицы
    Subcategory.objects.all().delete()
    Category.objects.all().delete()
    Good.objects.all().delete()
    Image.objects.all().delete()
    for item in os.listdir(DATA_DIR):
        if os.path.isdir(os.path.join(DATA_DIR,item)):
            # читаем meta.yml
            with open(os.path.join(DATA_DIR,item,'meta.yml'),'r') as f:
                rez = f.read()
            # парсим yml формат
            yml_data = yaml.load(rez)
            print(yml_data['name_ru'])
            # создаем категории в базе если такой нет
            try: 
                cat = Category.objects.get(name_slug=yml_data['parent_slug'])
            except:
                cat = Category()

                cat.name = yml_data['parent_name_ru']
                cat.name_slug = yml_data['parent_slug']
                cat.save()
            # создаем подкатегории в базе если такой нет
            try: 
                scat = Subcategory.objects.get(name_slug=yml_data['name_slug'])
            except:
                scat = Subcategory()
                scat.name = yml_data['name_ru']
                scat.name_slug = yml_data['name_slug']
                scat.category = cat
                scat.save()

            for item_good in os.listdir(os.path.join(DATA_DIR,item)):
                if os.path.isdir(os.path.join(DATA_DIR,item,item_good)):
                    import_goods(item_good,os.path.join(DATA_DIR,item),scat)

# функция импорта товаров

def import_goods(name_slug,path,sub_category):
    print('Importing ..... %s' % name_slug)
    # читаем meta.yml
    try:
        with open(os.path.join(path,name_slug,'meta.yml'),'r') as f:
            rez = f.read()
    except:
        return False
    # парсим yml формат
    yml_data = yaml.load(rez) 
    # сохраняем позицию товара
    g = Good()
    g.name = yml_data['name_ru']
    g.name_slug = yml_data['name_slug']
    g.desc = yml_data['description_ru']
    g.subcategory = sub_category
    g.save()

    # сохраняем картинки
    path_to_image = os.path.join(path,name_slug,'images','1.png')
    #print(path_to_image)

    img = Image()
    img.good = g
    img.save()
    file_name = '%s.png' % g.id
    with open(path_to_image, 'rb') as image_file:
        img.image.save(file_name,File(image_file),save=True)

class Command(BaseCommand):
    def handle(self, *args, **options):
        print('Importing data')
        import_catalog()


Выводы

В этой статье мы рассмотрели процесс создания приложения на базе фреймворка Django. Рассказали о том, как создавать модель данных, создавать и кастомизировать админ-интерфейс для модели данных. Также мы осветили процесс создания консольной команды для заполнения базы данных информацией из файловой системы, которая была ранее загружена из сайта-примера.

 

0/5
Проголосовало людей: 0
СОДЕРЖАНИЕ
СТАТЬИ
Создание проекта Django и модели приложения
Админ-интерфейс
Выводы
Создание карточной игры Blackjack
Дмитрий Жариков
Дмитрий Жариков
Зачем бизнесу имиджевый сайт
Термин «веб-представительство» как нельзя точнее передает суть имиджевого сайта. Это ваш онлайн-ресепшн, который формирует положительное…
Алексей Варламов
Алексей Варламов
IM на основе NodeJS Express и Angular 7
Дмитрий Жариков
Дмитрий Жариков
ПОЛУЧАТЬ ИНТЕРЕСНЫЕ СТАТЬИ
Уже подписались 242 человек
Автор
134
0
Дмитрий Жариков
Дмитрий
Жариков
Возможно
Все есть яд и все есть лекарство. То же касается и копирайтинга. У него благая…
Наталья Семенютенко
Наталья Семенютенко
Как наши SEO-специалисты используют Serpstat. Плюсы и минусы сервиса на конкретном примере.
Сергей Мищенко
Сергей Мищенко
Грамотный процесс разработки интернет сайта является достаточно сложным процессом, который выполняется в несколько этапов. И…
Wezom
Wezom
Давайте начнем
беседу!
КОММЕНТАРИИ0
ОСТАВИТЬ КОММЕНТАРИЙ К СТАТЬЕ
ПОДПИСЫВАЙТЕСЬ НА РАССЫЛКУ АЙТЫЖБЛОГ
ХОТИТЕ ПОЛУЧАТЬ 
ИНТЕРЕСНЫЕ СТАТЬИ?
Уже подписались 242 человек
313
ПОПИСЧИКОВ
ЧИТАТЬ
4295
ПОПИСЧИКОВ
СЛЕДИТЬ
9307
ПОПИСЧИКОВ
СЛЕДИТЬ