Разработка Веб-сайтов, поддержка движков, веб-фреймворки и всё про них
Если мы попытаемся тестировать ошибку 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, к примеру.
Часто появляется необходимость в дополнительных полях для личного профиля пользователя, которые он может заполнить. Для этого необходимо создать отдельную модель в основном 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 %}
Так выглядит это на работающем сайте:
Для экспорта данных, в данном случае страницы 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-файле мы видим вместо черных квадратиков на месте русских букв нормальные кириллические символы:
В данной статье хотелось бы затронуть мобильную тематику в популярной RouterOS, а именно подключение 3G/4G(LTE) модемов к роутерам компании MikroTik
Продолжить Чтение...Развернутая инструкция по настройке nginx, Gunicorn и SSL для вашего Django проекта в продакшене.
Продолжить Чтение...Кратчайшая минималистичная инструкция по сборке кластера на ESXI и немного про резервирование
Продолжить Чтение...