Debian, Dojo, Django, Python

Делюсь опытом в описанных технологиях. Блог в первую очередь выполняет роль памяток для меня самого.

Показаны сообщения с ярлыком пользователь. Показать все сообщения
Показаны сообщения с ярлыком пользователь. Показать все сообщения

Проблемы с кириллицей в имени пользователя Windows

При использовании Atom столкнулся с тем, что некоторые плагины отказывались работать, выдавая странные сообщения в консоль. Сначала я решал проблему тем, что правил переменную PATH на уровне системы и на уровне пользователя, однако, это особого эффекта не имело, т.к. стоило только поставить более новую версию io.js, как PATH тут же сбрасывалась к примерно такому виду:

    C:\Python34\;C:\Python34\Scripts;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Program Files (x86)\Git\cmd;C:\Program Files (x86)\iojs\;C:\Users\%30o$321\AppData\Roaming\npm

Из этого видно, что портится путь к текущей системной папке пользователя. Изменение свойств папок "Видео", "Изображения", "Документы" и т.д. тут не может ничем помочь. Я использую учётную запись Microsoft со всеми её удобствами вроде OneDrive, WindowsPhone и т.д. и отказываться от неё в пользу локальной учётной записи не хотел (не за то деньги плачены). Поиск привёл меня к решению.

На первом этапе нам нужно создать дополнительную учётную запись типа "Администратор". Способ создания значения не имеет, я рекомендую через "Панель управления" (пользователи Windows 8.1 непрофессиональной редакции выбора не имеют). Теперь нужно загрузиться в безопасном режиме и войти в систему под новосозданной учётной записью. После этого нужно запустить редактор реестра - Regedit. Следует открыть раздел реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList. В нём содержится несколько подразделов. Если щёлкнуть на одном из них, то появится список ключей, одним из которых будет ProfileImagePath. Заменяем имеющееся значение на нужное, например, вот так:

C:\Users\Максим -> C:\Users\xPhoenix

Так же следует обязательно переименовать имеющуюся папку профиля. После этого следует перезагрузить компьютер и при необходимости удалить созданную ранее дополнительную учётную запись администратора.

Проблема с кириллицей в имени пользователя наблюдается и в некоторых других программах. Насколько помню, из-за неё среда MATLAB 7.5 вообще отказывалась сохранять файлы.

Собственная модель пользователя Django (>=1.8)

Введение

Статья была обновлена 5 июня 2015 года и содержит исправление некоторых ошибок и дополнительную информацию.
На GitHub был опубликован репозиторий с исходными кодами, которые содержат ряд исправлений и дополнений. Нашли ошибку? Создайте pull-request или issue, я обязательно посмотрю.

На данную тему в Интернете уже написано огромное множество статей, и моя станет лишь очередной попыткой описать то, что уже и так широко известно. Не претендуя на оригинальность, я попробую описать тот способ, которым пользуюсь сам. В статье пойдёт речь о расширении стандартной модели User, имеющейся в Django.

В настоящее время так же широко используются ещё два способа:

  • Создание связанной с пользователем 1 к 1 модели профиля.
  • Полная замена стандартной модели пользователя на свою с последующим переписыванием бэкэндов авторизации.

Первый способ лично мне не нравится своей устарелостью. Ещё в Django 1.5 была проделана огромная работа по рефакторингу существующих модулей, связанных с пользователями, что дало возможность расширять имеющуюся модель. К тому же, способ с дополнительной моделью заставляет вас писать дополнительный код, например, на отлов события создания или удаления модели пользователя.

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

Создание расширенной модели

Создавать новую модель пользователя мы будем на основе уже имеющейся в Django, т.е. будем использовать наследование. Первым делом следует создать новое приложение Django:

djangoadmin.py startapp extuser

Мне нравится использовать имя extuser для решения поставленной задачи потому, что, оно полностью передаёт суть и назначение данного приложения - расширение стандартной модели пользователя.

Модифицируем файл models.py нового приложения.

extuser/models.py

from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin
from django.contrib.auth.models import BaseUserManager
from django.db import models


class UserManager(BaseUserManager):

    def create_user(self, email, password=None):
        if not email:
            raise ValueError('Email непременно должен быть указан')

        user = self.model(
            email=UserManager.normalize_email(email),
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password):
        user = self.create_user(email, password)
        user.is_admin = True
        user.save(using=self._db)
        return user


class ExtUser(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(
        'Электронная почта',
        max_length=255,
        unique=True,
        db_index=True
    )
    avatar = models.ImageField(
        'Аватар',
        blank=True,
        null=True,
        upload_to="user/avatar"
    )
    firstname = models.CharField(
        'Фамилия',
        max_length=40,
        null=True,
        blank=True
    )
    lastname = models.CharField(
        'Имя',
        max_length=40,
        null=True,
        blank=True
    )
    middlename = models.CharField(
        'Отчество',
        max_length=40,
        null=True,
        blank=True
    )
    date_of_birth = models.DateField(
        'Дата рождения',
        null=True,
        blank=True
    )
    register_date = models.DateField(
        'Дата регистрации',
        auto_now_add=True
    )
    is_active = models.BooleanField(
        'Активен',
        default=True
    )
    is_admin = models.BooleanField(
        'Суперпользователь',
        default=False
    )

    # Этот метод обязательно должен быть определён
    def get_full_name(self):
        return self.email

    # Требуется для админки
    @property
    def is_staff(self):
        return self.is_admin

    def get_short_name(self):
        return self.email

    def __str__(self):
        return self.email

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    objects = UserManager()

    class Meta:
        verbose_name = 'Пользователь'
        verbose_name_plural = 'Пользователи'

Рассмотрим этот код.

Менеджер моделей

Сначала идёт описание менеджера для данной модели. Описывать его нужно для того, чтобы правильно работали методы создания нового пользователя. Чуть ниже в коде будет указано, что для работы с объектами типа ExtUser нужно использовать именно его. Можно определить несколько менеджеров при необходимости, каждый из которых будет отвечать за свою часть работы.

Модель ExtUser

В этой модели как ключевое указано поле email. Я считаю, что это один из лучших способов для авторизации пользователей. Лучше может быть только двухфакторная авторизация через SMS. Создав ключевое поле, следуте обязательно указать, что оно используется в качестве имени пользователя:

USERNAME_FIELD = 'email'

Делали бы мы авторизацию через номер телефона - указали бы так:

phone = CharField(
    'Номер телефона'
    max_length=20,
    unique=True,
    db_index=True
)

#Чуть ниже в этом же классе:
USERNAME_FIELD = 'phone'

Надеюсь, общий принцип понятен. Дальше уже идёт отсебятина, которую можно не писать. Например, аватары, отчество и т.д. Список полей в каждом проекте будет разным. Не забудьте только описать методы проверки прав has_perm() и has_module_perms() соответственно.

Формы

Наш класс не будет работать, если не создать для него формы админки. Создадим в приложении файл forms.py.

extuser/forms.py

from django import forms
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from django.contrib.auth import get_user_model


class UserCreationForm(forms.ModelForm):
    password1 = forms.CharField(
        label='Пароль',
        widget=forms.PasswordInput
    )
    password2 = forms.CharField(
        label='Подтверждение',
        widget=forms.PasswordInput
    )

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError('Пароль и подтверждение не совпадают')
        return password2

    def save(self, commit=True):
        user = super(UserCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data['password1'])
        if commit:
            user.save()
        return user

    class Meta:
        model = get_user_model()
        fields = ('email',)


class UserChangeForm(forms.ModelForm):

    '''
    Форма для обновления данных пользователей. Нужна только для того, чтобы не
    видеть постоянных ошибок "Не заполнено поле password" при обновлении данных
    пользователя.
    '''
    password = ReadOnlyPasswordHashField(
        widget=forms.PasswordInput,
        required=False
    )

    def save(self, commit=True):
        user = super(UserChangeForm, self).save(commit=False)
        password = self.cleaned_data["password"]
        if password:
            user.set_password(password)
        if commit:
            user.save()
        return user

    class Meta:
        model = get_user_model()
        fields = ['email', ]


class LoginForm(forms.Form):

    """Форма для входа в систему
    """
    username = forms.CharField()
    password = forms.CharField()

Здесь описаны две формы - для создания нового пользователя и для смены пароля. Т.к. я использую Django REST Framework, я сделал эти формы для того, чтобы корректно работало обновление модели пользователя, т.к. поле password является обязательным. Если отправить запрос на указание, например, нового отчества, будет возвращена ошибка, т.к. поле пароля должно быть обязательно заполнено. Дополнительная форма решает эту проблему.

Как правило, ошибка обновления модели в DRF происходит при полном обновлении модели. Для частитчного обновления нужно указывать в заголовке HTTP-запроса метод PATCH вместо PUT.

Пожалуй, единственное, на что тут нужно обратить внимание - это описание связи наших форм с моделью пользователя. Если в будущем понадобится создать новую модель или переименовать её, переписывать код не придётся, т.к. используется функция get_user_model(), возвращающая класс модели пользователя, используемый для авторизации. Если проще, то эта функция возвращает класс, указанный в параметре AUTH_USER_MODEL в файле settings.py нашего приложения.

Админка

Изменения придётся внести и в файл admin.py:

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import Group

from .forms import UserChangeForm
from .forms import UserCreationForm
from .models import ExtUser


class UserAdmin(UserAdmin):
    form = UserChangeForm
    add_form = UserCreationForm

    list_display = [
        'date_of_birth',
        'email',
        'firstname',
        'is_admin',
        'lastname',
        'middlename',
    ]

    list_filter = ('is_admin',)

    fieldsets = (
                (None, {'fields': ('email', 'password')}),
                ('Personal info', {
                 'fields': (
                     'avatar',
                     'date_of_birth',
                     'firstname',
                     'lastname',
                     'middlename',
                 )}),
                ('Permissions', {'fields': ('is_admin',)}),
                ('Important dates', {'fields': ('last_login',)}),
    )

    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': (
                'date_of_birth',
                'email',
                'password1',
                'password2'
            )}
         ),
    )

    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ()

# Регистрация нашей модели
admin.site.register(ExtUser, UserAdmin)
admin.site.unregister(Group)

Настройка Django

Пришло время переустановить Windows внести изменения в файл settings.py.

settings.py

# Тут должен быть импорт нужных модулей.

SECRET_KEY = # Какой-то ключ, автоматически созданный Django

DEBUG = True
TEMPLATE_DEBUG = True
ALLOWED_HOSTS = []

EXTERNAL_APPS = (
    # У меня, например, здесь разные внешние библиотеки
)

INTERNAL_APPS = (
    # Тут список наших приложений
    'extuser',
    # А тут его продолжение
)

DJANGO_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.sites',
)

INSTALLED_APPS = EXTERNAL_APPS + INTERNAL_APPS + DJANGO_APPS

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.locale.LocaleMiddleware',
)

TEMPLATE_CONTEXT_PROCESSORS = (
    'django.core.context_processors.csrf',
    'django.contrib.auth.context_processors.auth',
    'django.core.context_processors.debug',
    'django.core.context_processors.request',
)

AUTHENTICATION_BACKENDS = (
    "django.contrib.auth.backends.ModelBackend",
)

LOGIN_URL = r"/login/"

AUTH_USER_MODEL = 'extuser.ExtUser'

Миграции

Заключительный этап настройки нашей модели - проведение миграций. Запустите скрипт:

python manage.py migrate

Для создания нового пользователя-администратора используйте команду createsuperuser:

python manage.py createsuperuser