click fraud detection
click fraud detection
Blog Case

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

BLOG
CASE
4955
4.5/ 5stars
4.5/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. Рассказали о том, как создавать модель данных, создавать и кастомизировать админ-интерфейс для модели данных. Также мы осветили процесс создания консольной команды для заполнения базы данных информацией из файловой системы, которая была ранее загружена из сайта-примера.

У ВАС ОСТАЛИСЬ ВОПРОСЫ?

Оставьте ваши контактные данные. Наш менеджер свяжется и проконсультирует вас.

ПОЛУЧИТЬ КОНСУЛЬТАЦИЮ

Наш менеджер свяжется с Вами в ближайшее время

4.5/5
Полезность
Проголосовали 2
Как вам статья?
Дмитрий Жариков
Дмитрий Жариков
most
Popular
Возможно
Советы, как правильно выбрать доменное имя для вашего ресурса. На что стоит больше обратить внимание.
Галина Назарова
Галина Назарова
Мобильное приложение по доставке еды помогает сэкономить клиенту массу времени, ведь человек сможет ознакомиться с…
Wezom
Wezom
Давайте начнем
беседу!
КОММЕНТАРИИ0
ОСТАВИТЬ КОММЕНТАРИЙ К СТАТЬЕ
ПОДПИСЫВАЙТЕСЬ НА РАССЫЛКУ АЙТЫЖБЛОГ
ХОТИТЕ ПОЛУЧАТЬ 
ИНТЕРЕСНЫЕ СТАТЬИ?
СЛЕДИТЕ ЗА НАМИ В СОЦИАЛЬНЫХ СЕТЯХ