• [ GEEKHERO:2021 ]
  • Категории
  • Files 42

Воистину гений

СТАТЬЯ ИЛИ СОН?
Полезности разработчика Django #1 | GEEKHERO
root 20 марта 2021 г. 19:41 | обновлен 5 апреля 2021 г. 13:44

Полезности разработчика Django #1

Иконка категории
В категории "Web-разработка"

Разработка Веб-сайтов, поддержка движков, веб-фреймворки и всё про них

Метки: django, python, веб, pdf, profile, formset, pisa, xhtml2pdf, 404


Тестирование handler 404

Если мы попытаемся тестировать ошибку 404 при заданном debug = True, то будет получать стандартный для Django отчет об ошибке с указанием о причине, но используя следующий метод вы сможете проверить работоспособность отработки 404 ошибки без лишних забот. На проде настоятельно рекомендую использовать nginx.

1. Открываем для редактирования файл settings.py, находящийся в каталоге проекта и устанавливаем значение debug = False

2. В том же каталоге открываем для редактирования файл urls.py и добавляем следующие строки:

​​​​​​​from django.views.static import serve #добавляем в заголовке

re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),

re_path(r'^static/(?P<path>.*)$', serve, {'document_root': settings.STATIC_URL}),

При переключении debug в значение false, мы по умолчанию теряем статику и медиа, но используя данный метод, django продолжить обрабатывать эти данные вместо nginx, к примеру.

Расширение профиля пользователя - O2OField

Часто появляется необходимость в дополнительных полях для личного профиля пользователя, которые он может заполнить. Для этого необходимо создать отдельную модель в основном application, где ключевым полем будет выступать user - поле 1-к-1 к встроенному полю User, выглядит это на примере так:

from django.db.models.signals import post_save # это добавляем в заголовок


class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name='Имя пользователя')
    about = models.TextField(max_length=400, default='', verbose_name='Обо мне', blank=True, null=True)
    website = models.URLField(default='', verbose_name='Веб сайт', blank=True, null=True)
    avatar = models.ImageField(upload_to='profile/avatar/', blank=True, verbose_name='Аватар',
                               default='profile/avatar/defaultava.png')
    cover = models.ImageField(upload_to='profile/cover/', blank=True, verbose_name='Обложка',
                              default='profile/cover/defaultcover.jpg')

    def __str__(self):
        return self.user.username

    class Meta:
        verbose_name = 'профиль пользователя'
        verbose_name_plural = 'профили пользователей'

# С помощью следующих строк, для новых пользователей будет автоматически создаваться запись в данном модели
def create_profile(sender, **kwargs):
    user = kwargs['instance']
    if kwargs['created']:
        user_profile = UserProfile.objects.create(user=user)
        user_profile.save()


post_save.connect(create_profile, sender=User)

Далее, во views.py добавляем следующее:

def get_user_profile(request, username):
    profile = User.objects.get(username=username)
    posts = post.objects.filter(status=True, author=profile).order_by('created_date').reverse()

    return render(request, 'site/profile.html', {
        "user": profile,
        "posts": posts,
    })

После, добавляем строку в urls.py:

url(r'user/(?P<username>[a-zA-Z0-9]+)$', views.get_user_profile, name='get_user_profile'),

А в шаблон HTML для отображения пользовательских данных следующее: {{user.userprofile.telegram}} - отобразит поле telegram, если оно было заполнено для конкретного пользователя.

 

Формсеты и динамическое добавление форм

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

Стояла у меня такая задача: отображать форму, а по нажатию на кнопку добавлять дополнительные экземпляры данной формы. 

Для этих целей создал несколько моделей вида, где Worker - это FK для Experience

class Worker(models.Model):
    public_cv = models.BooleanField(default=False, verbose_name='Can everyone see your resume ?')
    cv_name = models.CharField(max_length=250, verbose_name='CV name', blank=True)
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name='Author', default=0)


    def __str__(self):
        return self.name

    def publish(self):
        self.published_date = timezone.now()
        self.save()

class Experience(models.Model):
    worker = models.ForeignKey(Worker, on_delete=models.CASCADE)
    title = models.CharField(max_length=200, verbose_name='Position name')

    def __str__(self):
        return self.title

    def publish(self):
        self.published_date = timezone.now()
        self.save()

# +много других моделей и полей

Следующим шагом, который приблежал меня к цели - реализовать желаемое сначала в административной панели django-admin, для этого я использовал StackedInline:

class ExperienceInstance(admin.StackedInline):
    model = Experience
    extra = 1

@admin.register(Worker)
class PublishWorkers(admin.ModelAdmin):
    inlines = [
        ExperienceInstance,]

И получим желаемый вид пока что в Django-admin, создается пустая форма Experience связанная с Worker и кнопка "Добавить форму Experience":

​​​​​​​

 

Теперь нужно добавить во views код, который позволит выводить форму Experience отдельно и по нажатии кнопки создавать дополнительный экземпляр формы Experience, будем использовать Formset:

from django.forms import inlineformset_factory
from django.http import HttpResponseRedirect
from .forms import ExperienceForm


def expformview(request, worker_uid):
    worker = Worker.objects.get(uid=worker_uid)
    ExperienceFormset = inlineformset_factory(
        Worker, Experience, form=ExperienceForm, extra=1, max_num=15, can_delete=True
    )
    if request.method == 'POST':
        formset = ExperienceFormset(request.POST, instance=worker)
        if formset.is_valid():
            formset.save()
            return HttpResponseRedirect(request.META.get('HTTP_REFERER'))

    formset = ExperienceFormset(instance=worker)
    return render(request, 'site/expform.html',
                  {
                      'formset': formset,
                      'worker': worker,
                  }
                  )

Также, создадим Форму в forms.py ExperienceForm:

class ExperienceForm(forms.ModelForm):
    started = forms.DateField(
        required=False,
        label='Start date',
        widget=forms.TextInput(attrs={'placeholder': 'YYYY-MM-DD'})
    )
    ended = forms.DateField(
        required=False,
        label='End date',
        widget=forms.TextInput(attrs={'placeholder': 'YYYY-MM-DD'})
    )

    class Meta:
        model = Experience
        fields = ('title',
                  'selfedu',
                  'company',
                  'place',
                  'actual',
                  'started',
                  'ended',
                  'tasks',
                  )

 

Далее шаблон HTML. Я использую Crispy для лучшего отображения полей форм. {{formset.media}} нужен для вывода WYSIWYG-редактора ckeditor. При нажатии на кнопку с type="submit" данные текущей формы сохраняются в базы и снизу добавляется еще один, но пустой экземпляр формы:

{% extends 'site/base.html' %}
{% load crispy_forms_tags %}
{% block content %}
{% if worker.author == request.user%}
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>Experience | {{worker}}</title>
   </head>
   <body>
      <center>
         <div class="col-lg-5" style="margin:1em;">
            <nav aria-label="breadcrumb">
               <ol class="breadcrumb">
                   <li class="breadcrumb-item">Basic information</li>
                   <li class="breadcrumb-item active" aria-current="page"><b>Experience</b></li>
                   <li class="breadcrumb-item">Education</li>
                   <li class="breadcrumb-item">Certification</li>
                   <li class="breadcrumb-item">Awards</li>
                   <li class="breadcrumb-item">Projects</li>
               </ol>
            </nav>
         </div>
      </center>
   <h2 align="center" style="margin:1em;">{{worker}}'s Experience form</h2>
      <form method="post">
         {% csrf_token %}
         <div class="row" style="margin:2em 0 2em 0;">
            <div class="col-lg-5 mx-auto">
               {{formset.media}}
               {{formset|crispy}}
            </div>
             <div class="col-lg-12">
                 <center><button type="submit" class="btn btn-outline-warning">Save & Add</button>
                 <a href="edu"><button type="button" class="btn btn-outline-success">Next > Education</button></a></center>
             </div>
         </div>
      </form>
   </body>
</html>
{%else%}
      <div class="row">
         <div class="col-lg-12" style="margin-top:6em;">
            <center>
               <h2>You have not access to this section</h2>
            </center>
         </div>
      </div>
      {%endif%}
{% endblock %}

Так выглядит это на работающем сайте: 

​​​​​​​

 

 

Экспорт данных в PDF с поддержкой кириллицы (русских символов / букв)

Для экспорта данных, в данном случае страницы HTML в PDF мы будем использовать XHTML2PDF; для его установки необходимо в venv запустить: 

pip install xhtml2pdf

Далее добавляем следующий код в views.py:

from xhtml2pdf import pisa

def render_pdf_view(request, worker_uid):
    template_path = 'site/pdf.html'
    worker = Worker.objects.get(uid=worker_uid)
    exp = Experience.objects.filter(worker=worker)
    context = {
        'worker': worker,
        'exp': exp,
    }
    response = HttpResponse(content_type='application/pdf')

    response['Content-Disposition'] = 'filename="%s_%s.pdf"' % (worker.name, worker.created_date.strftime('%Y-%m-%d')) # правлю название выходного файла PDF вида: Имя_Год-М-Д
    # Найти шаблон и вывести его
    template = get_template(template_path)
    html = template.render(context)

    # Создаем PDF
    pisa_status = pisa.CreatePDF(html, dest=response, )
    # Вывод ошибок
    if pisa_status.err:
        return HttpResponse('We had some errors <pre>' + html + '</pre>')
    return response

Шаблон HTML заполянем как обычный шаблон, но нужно придерживаться правила, что парсер PDF видит только локальные стили, поэтому их нужно объявить между тегами <style></style> в данном шаблоне.

Чтобы русские символы корректно отображались в экспортируемом PDF необходимо загрузить шрифт с поддержкой кириллических (русских) букв и положить его в static/fonts/ , при этом указать до файла-шрифта полный пуст с учетом системных каталогов, например в моем случае путь выкглядит так: /var/www/cvmaker/static/fonts/arial.ttf , а в между тегами style добавляем следующее:

@font-face {
         font-family: 'sans-serif';
         src: url("/var/www/cvmaker/static/fonts/arial.ttf");
         }
         body{
         font-family: "sans-serif";
         }

Таким образом в экспортируемом PDF-файле мы видим вместо черных квадратиков на месте русских букв нормальные кириллические символы:

Поделиться записью


Похожие записи:

Card image cap
MikroTik 3G 4G LTE: Мобильный роутер – из LTE в PPP

В данной статье хотелось бы затронуть мобильную тематику в популярной RouterOS, а именно подключение 3G/4G(LTE) модемов к роутерам компании MikroTik

Продолжить Чтение...
Card image cap
Запуск Django сайта на nginx + Gunicorn + SSL

Развернутая инструкция по настройке nginx, Gunicorn и SSL для вашего Django проекта в продакшене.

Продолжить Чтение...
Card image cap
Собираем кластер на ESXI

Кратчайшая минималистичная инструкция по сборке кластера на ESXI и немного про резервирование

Продолжить Чтение...
Категории
Обучение и подготовка (3)
Linux (15)
Телекоммуникации (12)
Программирование (2)
Обзоры на технику (1)
Виртуализация (1)
Web-разработка (5)
Администрирование Windows (0)

Последние записи
Быстро подключаемся к серверам по SSH из терминала
28 Фев 2023
Flask в Docker+Kuberenetes
28 Авг 2022
Готовим образ Docker для Flask приложения
26 Авг 2022

Популярные метки
Copyright 2019-2021  
Обратная связь