Rozszerzanie modelu użytkownika o niestandardowe pola w Django

442

Jaki jest najlepszy sposób rozszerzenia modelu użytkownika (w pakiecie z aplikacją uwierzytelniającą Django) o pola niestandardowe? Chciałbym również użyć adresu e-mail jako nazwy użytkownika (do celów uwierzytelnienia).

Widziałem już kilka sposobów, aby to zrobić, ale nie mogę zdecydować, który z nich jest najlepszy.

Farinha
źródło
24
Większość odpowiedzi jest nieaktualna / przestarzała. Zobacz stackoverflow.com/a/22856042/781695 i stackoverflow.com/q/14104677/781695 & stackoverflow.com/q/16880461/781695
użytkownik
@buffer - Pierwsze pytanie, do którego linkujesz (stackoverflow.com/a/22856042/781695) zostało teraz usunięte. Pozostałe są nadal aktualne.
Tony
3
Aby użyć adresu e-mail zamiast nazwy użytkownika do uwierzytelnienia, ten artykuł jest przydatny.
Jestem bardzo zafascynowany tym, że pytanie zostało zadane w 2008 roku, zanim Django został wydany @Farinha
Tessaracter

Odpowiedzi:

253

Najmniej bolesnym i rzeczywiście polecanym przez Django sposobem na zrobienie tego jest OneToOneField(User)własność.

Rozszerzenie istniejącego modelu użytkownika

Jeśli chcesz przechowywać związane z tym informacje User, możesz użyć relacji jeden do jednego z modelem zawierającym pola, aby uzyskać dodatkowe informacje. Ten model jeden do jednego jest często nazywany modelem profilu, ponieważ może przechowywać informacje niezwiązane z uwierzytelnianiem na temat użytkownika witryny.

To powiedziawszy, rozszerzenie django.contrib.auth.models.Useri zastąpienie działa również ...

Zastępowanie niestandardowego modelu użytkownika

Niektóre rodzaje projektów mogą mieć wymagania uwierzytelniania, dla których wbudowany Usermodel Django nie zawsze jest odpowiedni. Na przykład w niektórych witrynach sensowniej jest używać adresu e-mail jako tokena identyfikacyjnego zamiast nazwy użytkownika.

[Ed: Dwa ostrzeżenia i powiadomienie , że jest to dość drastyczne .]

Na pewno trzymałbym się z daleka od zmiany faktycznej klasy User w twoim drzewie źródłowym Django i / lub kopiowania i modyfikowania modułu auth.

Ryan Duffield
źródło
51
Do Twojej wiadomości zalecaną nową (1.0+) metodą jest OneToOneField (Użytkownik) docs.djangoproject.com/en/dev/topics/auth/…
Dave Forgac
2
Shawn Rider z PBS podał kilka naprawdę dobrych powodów, dla których nie powinieneś rozszerzać django.contrib.auth.models.User. Zamiast tego użyj OneToOneField (użytkownika).
pydanny
2
user = models.ForeignKey(User, unique=True)czy to to samo co user = models.OneToOneField(User)? Myślałem, że efekt końcowy jest taki sam? Ale może implementacja w backendie jest inna.
Sam Stoelinga,
7
Czy ktoś może odwołać się do argumentu / uzasadnienia Shawn Riders?
Jeremy Blanchard
1
Oto kilka dodatkowych informacji na temat rozszerzania modeli użytkowników od wersji django 1.7
Derek Adair
226

Uwaga: ta odpowiedź jest nieaktualna. zobacz inne odpowiedzi, jeśli używasz Django 1.7 lub nowszego.

Tak to robię.

#in models.py
from django.contrib.auth.models import User
from django.db.models.signals import post_save

class UserProfile(models.Model):  
    user = models.OneToOneField(User)  
    #other fields here

    def __str__(self):  
          return "%s's profile" % self.user  

def create_user_profile(sender, instance, created, **kwargs):  
    if created:  
       profile, created = UserProfile.objects.get_or_create(user=instance)  

post_save.connect(create_user_profile, sender=User) 

#in settings.py
AUTH_PROFILE_MODULE = 'YOURAPP.UserProfile'

Spowoduje to utworzenie profilu użytkownika za każdym razem, gdy użytkownik zostanie zapisany, jeśli został utworzony. Następnie możesz użyć

  user.get_profile().whatever

Oto więcej informacji z dokumentów

http://docs.djangoproject.com/en/dev/topics/auth/#storing-additional-information-about-users

Aktualizacja: należy pamiętać, że AUTH_PROFILE_MODULEjest przestarzała od wersji 1.5: https://docs.djangoproject.com/en/1.5/ref/settings/#auth-profile-module

rodzynki
źródło
6
Dzięki za jasny przykład, zauważ, że def create_user .... nie jest częścią klasy UserProfile i powinien być wyrównany do lewej.
PhoebeB,
5
Dzięki temu rozwiązaniu inne modele ForeignKey dla użytkownika lub UserProfile?
andrewrk
9
Inne modele powinny używać user = models.ForeignKey( User )i pobierać obiekt profilu za pośrednictwem user.get_profile(). Pamiętaj, aby from django.contrib.admin.models import User.
Craig Trader,
1
Korzystając z tej metody, muszę się rozdzielić, gdy odzyskuję zwykłe informacje (imię i nazwisko, hasło) i niestandardowe lub czy istnieje sposób, aby to zrobić od razu? To samo dotyczy tworzenia nowego użytkownika?
Martin Trigaux,
5
Ta odpowiedź i komentarze stały się nieaktualne, np. AUTH_PROFILE_MODULE jest nieaktualny,User
użytkownik
196

Minęło trochę czasu od 2008 roku i czas na świeżą odpowiedź. Od wersji Django 1.5 będziesz mógł tworzyć niestandardowe klasy użytkowników. Właściwie w chwili, gdy to piszę, jest on już scalony w master, więc możesz go wypróbować.

Jest trochę informacji na ten temat w dokumentach lub jeśli chcesz zagłębić się w to, w tym zatwierdzeniu .

Wszystko, co musisz zrobić, to dodać AUTH_USER_MODELustawienia ze ścieżką do niestandardowej klasy użytkownika, która rozszerza albo AbstractBaseUser(bardziej konfigurowalną wersję), albo AbstractUser(mniej lub bardziej starą klasę użytkownika, którą możesz rozszerzyć).

Dla osób, które leniwie klikają, oto przykładowy kod (zaczerpnięty z dokumentów ):

from django.db import models
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser
)


class MyUserManager(BaseUserManager):
    def create_user(self, email, date_of_birth, password=None):
        """
        Creates and saves a User with the given email, date of
        birth and password.
        """
        if not email:
            raise ValueError('Users must have an email address')

        user = self.model(
            email=MyUserManager.normalize_email(email),
            date_of_birth=date_of_birth,
        )

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

    def create_superuser(self, username, date_of_birth, password):
        """
        Creates and saves a superuser with the given email, date of
        birth and password.
        """
        u = self.create_user(username,
                        password=password,
                        date_of_birth=date_of_birth
                    )
        u.is_admin = True
        u.save(using=self._db)
        return u


class MyUser(AbstractBaseUser):
    email = models.EmailField(
                        verbose_name='email address',
                        max_length=255,
                        unique=True,
                    )
    date_of_birth = models.DateField()
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)

    objects = MyUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['date_of_birth']

    def get_full_name(self):
        # The user is identified by their email address
        return self.email

    def get_short_name(self):
        # The user is identified by their email address
        return self.email

    def __unicode__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        "Does the user have a specific permission?"
        # Simplest possible answer: Yes, always
        return True

    def has_module_perms(self, app_label):
        "Does the user have permissions to view the app `app_label`?"
        # Simplest possible answer: Yes, always
        return True

    @property
    def is_staff(self):
        "Is the user a member of staff?"
        # Simplest possible answer: All admins are staff
        return self.is_admin
Ondrej Slinták
źródło
Wygląda na to, że funkcja create_user nie przechowuje nazwy użytkownika?
Orca,
6
Ponieważ w tym przykładzie emailjest to nazwa użytkownika.
Ondrej Slinták
5
Musisz dodać unique=Truedo pola adresu e-mail, aby zezwolić na USERNAME_FIELDakceptację
Richard de Wit
Cześć, próbowałem utworzyć niestandardowego użytkownika, jak powiedziałeś, ale nie mogłem się zalogować przy użyciu adresu e-mail niestandardowego użytkownika. nie miałbyś nic przeciwko, by powiedzieć dlaczego?
Lionel,
Naprawdę nie mogę ci pomóc bez szczegółów. Byłoby lepiej, gdybyś utworzył nowe pytanie.
Ondrej Slinták
47

Od wersji Django 1.5 możesz łatwo rozszerzyć model użytkownika i zachować jedną tabelę w bazie danych.

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import ugettext_lazy as _

class UserProfile(AbstractUser):
    age = models.PositiveIntegerField(_("age"))

Musisz również skonfigurować go jako bieżącą klasę użytkownika w pliku ustawień

# supposing you put it in apps/profiles/models.py
AUTH_USER_MODEL = "profiles.UserProfile"

Jeśli chcesz dodać wiele preferencji użytkowników, lepszym wyborem może być opcja OneToOneField.

Uwaga dla osób tworzących biblioteki stron trzecich: jeśli potrzebujesz dostępu do klasy użytkownika, pamiętaj, że ludzie mogą to zmienić. Skorzystaj z oficjalnego pomocnika, aby uzyskać odpowiednią klasę

from django.contrib.auth import get_user_model

User = get_user_model()
Riccardo Galli
źródło
3
Jeśli planujesz używać django_social_auth, zalecam użycie relacji OneToOne. NIE używaj tej metody, bo spowoduje to popsucie migracji.
Nimo,
@Nimo: Czy mógłbyś opracować lub zacytować referencję
użytkownik
@buffer, minęło sporo czasu, ale myślę, że próbowałem połączyć django_social_authwtyczkę i zdefiniować AUTH_USER_MODEL dla użytkownika autoryzacji społecznościowej. Następnie, kiedy uruchomiłem manage.py migrację, zepsuła moją aplikację. Kiedy zamiast tego użyłem modelu użytkownika autoryzacji społecznościowej jako relacji OneToOne opisanej tutaj: stackoverflow.com/q/10638293/977116
Nimo
3
Prawdopodobnie ma to związek ze Changing this setting after you have tables created is not supported by makemigrations and will result in you having to manually write a set of migrations to fix your schemaźródłem: docs.djangoproject.com/en/dev/topics/auth/customizing/…
użytkownik
23

Poniżej znajduje się inne podejście do rozszerzenia użytkownika. Wydaje mi się, że jest to bardziej jasne, łatwe, czytelne niż w przypadku dwóch podejść.

http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/

Stosując powyższe podejście:

  1. nie musisz używać user.get_profile (). newattribute, aby uzyskać dostęp do dodatkowych informacji związanych z użytkownikiem
  2. możesz bezpośrednio uzyskać dostęp do dodatkowych nowych atrybutów za pośrednictwem user.newattribute
Rama Vadakattu
źródło
1
Bardziej podoba mi się podejście Scotta, oparte na dziedziczeniu obiektu User, a nie bezpośrednio na modelu. Czy ktoś może powiedzieć, że takie podejście nie jest mądre?
BozoJoe
1
@BozoJoe - Właśnie natknąłem się na ten problem z importowaniem danych zrzutu, co wydaje się być konsekwencją użycia tej metody: stackoverflow.com/questions/8840068/...
Ben Regenspan
15

Możesz po prostu rozszerzyć profil użytkownika, tworząc nowy wpis za każdym razem, gdy użytkownik jest tworzony przy użyciu sygnałów zapisu postu Django

models.py

from django.db.models.signals import *
from __future__ import unicode_literals

class UserProfile(models.Model):

    user_name = models.OneToOneField(User, related_name='profile')
    city = models.CharField(max_length=100, null=True)

    def __unicode__(self):  # __str__
        return unicode(self.user_name)

def create_user_profile(sender, instance, created, **kwargs):
    if created:
        userProfile.objects.create(user_name=instance)

post_save.connect(create_user_profile, sender=User)

Spowoduje to automatyczne utworzenie instancji pracownika po utworzeniu nowego użytkownika.

Jeśli chcesz rozszerzyć model użytkownika i chcesz dodać dodatkowe informacje podczas tworzenia użytkownika, możesz użyć django-betterforms ( http://django-betterforms.readthedocs.io/en/latest/multiform.html ). Spowoduje to utworzenie formularza dodawania użytkownika ze wszystkimi polami zdefiniowanymi w modelu UserProfile.

models.py

from django.db.models.signals import *
from __future__ import unicode_literals

class UserProfile(models.Model):

    user_name = models.OneToOneField(User)
    city = models.CharField(max_length=100)

    def __unicode__(self):  # __str__
        return unicode(self.user_name)

forms.py

from django import forms
from django.forms import ModelForm
from betterforms.multiform import MultiModelForm
from django.contrib.auth.forms import UserCreationForm
from .models import *

class ProfileForm(ModelForm):

    class Meta:
        model = Employee
        exclude = ('user_name',)


class addUserMultiForm(MultiModelForm):
    form_classes = {
        'user':UserCreationForm,
        'profile':ProfileForm,
    }

views.py

from django.shortcuts import redirect
from .models import *
from .forms import *
from django.views.generic import CreateView

class AddUser(CreateView):
    form_class = AddUserMultiForm
    template_name = "add-user.html"
    success_url = '/your-url-after-user-created'

    def form_valid(self, form):
        user = form['user'].save()
        profile = form['profile'].save(commit=False)
        profile.user_name = User.objects.get(username= user.username)
        profile.save()
        return redirect(self.success_url)

addUser.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="." method="post">
            {% csrf_token %}
            {{ form }}     
            <button type="submit">Add</button>
        </form>
     </body>
</html>

urls.py

from django.conf.urls import url, include
from appName.views import *
urlpatterns = [
    url(r'^add-user/$', AddUser.as_view(), name='add-user'),
]
Atul Yadav
źródło
Cześć i dziękuję za tę odpowiedź. Nadal staram się zrozumieć, jak połączyć to wszystko razem w urls.py .. jakieś wskazówki?
sal
@sal Dodano przykład adresów URL, które możesz teraz sprawdzić
Atul Yadav,
Dzięki!!. To bardzo mi pomaga. Dobry przykład
Sunny Chaudhari
@AtulYadav .. jakiej wersji Django użyłeś?
Rido
8

Rozszerzanie modelu użytkownika Django (UserProfile) jak profesjonalista

Znalazłem to bardzo przydatne: link

Ekstrakt:

z importu django.contrib.auth.models Użytkownik

class Employee(models.Model):
    user = models.OneToOneField(User)
    department = models.CharField(max_length=100)

>>> u = User.objects.get(username='fsmith')
>>> freds_department = u.employee.department
Massimo Variolo
źródło
2
Gotowy. Nie rozumiem, dlaczego -1. Lepsza edycja niż głosowanie negatywne w tym przypadku.
Massimo Variolo,
3

Nowość w Django 1.5, teraz możesz stworzyć swój własny model użytkownika (co wydaje się być dobrym rozwiązaniem w powyższym przypadku). Zobacz „Dostosowywanie uwierzytelniania w Django”

Prawdopodobnie najfajniejsza nowa funkcja w wersji 1.5.

chanty
źródło
1
W rzeczy samej. Ale uważaj, że należy tego unikać, chyba że jest to konieczne. Wdrażanie własnego powodu z tego pytania jest całkowicie poprawne, jeśli nie masz nic przeciwko udokumentowanym konsekwencjom. Do prostego dodawania pól zaleca się związek ze zwykłym modelem użytkownika .
gertvdijk
2

To właśnie robię i moim zdaniem jest to najprostszy sposób, aby to zrobić. zdefiniuj menedżera obiektów dla nowego dostosowanego modelu, a następnie zdefiniuj model.

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

class User_manager(BaseUserManager):
    def create_user(self, username, email, gender, nickname, password):
        email = self.normalize_email(email)
        user = self.model(username=username, email=email, gender=gender, nickname=nickname)
        user.set_password(password)
        user.save(using=self.db)
        return user

    def create_superuser(self, username, email, gender, password, nickname=None):
        user = self.create_user(username=username, email=email, gender=gender, nickname=nickname, password=password)
        user.is_superuser = True
        user.is_staff = True
        user.save()
        return user



  class User(PermissionsMixin, AbstractBaseUser):
    username = models.CharField(max_length=32, unique=True, )
    email = models.EmailField(max_length=32)
    gender_choices = [("M", "Male"), ("F", "Female"), ("O", "Others")]
    gender = models.CharField(choices=gender_choices, default="M", max_length=1)
    nickname = models.CharField(max_length=32, blank=True, null=True)

    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    REQUIRED_FIELDS = ["email", "gender"]
    USERNAME_FIELD = "username"
    objects = User_manager()

    def __str__(self):
        return self.username

Nie zapomnij dodać tej linii kodu do settings.py:

AUTH_USER_MODEL = 'YourApp.User'

To właśnie robię i zawsze działa.

Milad Khodabandehloo
źródło
0

Proste i skuteczne podejście to models.py

from django.contrib.auth.models import User
class CustomUser(User):
     profile_pic = models.ImageField(upload_to='...')
     other_field = models.CharField()
NeerajSahani
źródło