Полезности для разработчика на Django

от автора

Предисловие

Для написания данной статьи был изучен очень большой пласт материала, разбросанного по всему Интернету, по форумам, чатам, сайтам-блогам, stackoverflow. Я собрал все воедино, так как это пригодится и мне и очень надеюсь, что другие разработчики на Django, также, останутся довольны данным материалом. Если есть что добавить (улучшить) или поправить, пожалуйста, пишите в комментариях или в Диалоги ( личные сообщения ) Хабр.

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

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

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

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

from django.urls import re_path 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_ROOT}),

При переключении debug в значение false, мы по умолчанию теряем статику и медиа, но используя данный метод, django продолжить обрабатывать эти данные вместо nginx, к примеру, а также, позволяет проверить отработку 404 или других ошибок в Django при работе на localhost, например при python manage.py runserver .

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

Для подготовки этого материала ушло достаточно много времени, сотни незакрытых вкладок в поисках полезной информации, а так множество вопросов в чатах разработчиков на Python/Django и даже появился на свет сайт для создания резюме с динамическим добавлением полей формы, где представлен и используется данный функционал.
(Демо учетная запись:
Логин: habrhabr
Пароль: pp#6JZ2\a7y=

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

Для этих целей создал несколько моделей вида, где 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.py код, который позволит выводить форму 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',                   )

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

Шаблон HTML
{% 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-файле мы видим вместо черных квадратиков на месте русских букв нормальные кириллические символы:

ссылка на оригинал статьи https://habr.com/ru/post/550784/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *