-2

I'm trying to learn auth django system and I have several questions.

I want to create multiple users with email authentication. So, I used AbtractBaseUser and AbtractBaseManager. I read django doc and this question on the website : Implementing multiple user types with Django 1.5

and I would like to use it. So, I try to implement the second point : all fields for my two user types are in 1 models and with user_type variable I can choose fields to show. My code is down below :

models.py

# -*- coding: utf-8 -*-

from django.db import models
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import (
    BaseUserManager, AbstractBaseUser
)

TYPE_USER = (
    ('student', _('Student')),
    ('employee', _('Employee')),
)

class MyuserManager(BaseUserManager):
    def create_user(self, email, last_name, first_name, date_of_birth, user_type, password=None):
        if not email:
            raise ValueError("users must have an email address")

        user = self.model(
            email         = self.normalize_email(email),
            last_name     = last_name,
            first_name    = first_name,
            date_of_birth = date_of_birth,
            user_type     = user_type,
        )

        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, last_name, first_name, date_of_birth, user_type):
        user = self.create_user(email,
            password      = password,
            date_of_birth = date_of_birth,
            last_name     = last_name,
            first_name    = first_name,
            user_type     = user_type,
        )
        user.is_admin = True
        user.is_staff = True
        user.save()
        return user

class MyUser(AbstractBaseUser):
    """Based model user"""
    email   = models.EmailField(
        verbose_name = 'email',
        max_length=255,
        unique=True,
    )
    last_name     = models.CharField(max_length=30)
    first_name    = models.CharField(max_length=30)
    date_of_birth = models.DateField()
    user_type     = models.CharField(_('Type'), choices=TYPE_USER, max_length=20, default='student')
    phone         = models.CharField(max_length=20, blank=True)
    friends       = models.ManyToManyField("self", blank=True)
    faculty       = models.ForeignKey(Faculty, null=True, blank=True)
    desk          = models.CharField(max_length=30, blank=True)
    campus        = models.ForeignKey(Campus, null=True, blank=True)
    job           = models.ForeignKey(Function, null=True, blank=True)
    cursus        = models.ForeignKey(Cursus, null=True, blank=True)
    year          = models.IntegerField(null=True, blank=True)
    is_active     = models.BooleanField(default=True)
    is_admin      = models.BooleanField(default=False)
    is_staff      = models.BooleanField(default=False)

    objects = MyuserManager()
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['last_name', 'first_name', 'date_of_birth', 'user_type']

Question : Do you think it's correct ? or should i create 1 class with common fields and 2 classes for distinct type of user ?

Next, i would like to use my own backend so i implement a backend.py file in my project

backend.py

from fbLike.models import MyUser
from django.contrib.auth.backends import ModelBackend


class EmailAuthBackend(ModelBackend):
    def authenticate(self, email=None, password=None, **kwargs):
        try:
            user = MyUser.objects.get(email=email)
            if user.check_password(password):
                return user
        except MyUser.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return MyUser.objects.get(pk=user_id)
        except:
            return None

For me, it seems that is a correct implementation.

Questions : But, how to proceed with multi-table inheritence ? is it possible ? how to know what is the type of user before check password if i have for example :

class BaseUser(AbstractBaseUser):
  email = models.EmailField(max_length=254, unique=True)
  # common fields


class Student(BaseUser):
  first_name = models.CharField(max_length=30)
  last_name = models.CharField(max_length=30)
  # ...


class employee(BaseUser):
  company_name = models.CharField(max_length=100)
  # ...

Finally, with my models.py, backend.py i would try to use the CRUD system in my views.py to create and update users.

Below my files :

forms.py

class StudentProfileForm(forms.ModelForm):
    phone = forms.CharField(required=True)
    faculty = forms.ModelChoiceField(Faculty.objects.all())
    campus = forms.ModelChoiceField(Campus.objects.all())
    cursus = forms.ModelChoiceField(Cursus.objects.all())
    year = forms.IntegerField(required=True)

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'first_name', 'last_name', 
                  'date_of_birth', 'phone', 'faculty', 'campus', 'cursus', 'year'
        )
        widgets = {
            'password': forms.PasswordInput(render_value=True),
        }

class EmployeeProfileForm(forms.ModelForm):
    date_of_birth = forms.DateField(widget = AdminDateWidget)
    phone = forms.CharField(required=True)
    faculty = forms.ModelChoiceField(Faculty.objects.all())
    campus = forms.ModelChoiceField(Campus.objects.all())
    job = forms.ModelChoiceField(Function.objects.all())
    desk = forms.IntegerField(required=True)

    class Meta:
        model = MyUser
        fields = ('email', 'password', 'first_name', 'last_name', 
                  'date_of_birth', 'phone', 'faculty', 'campus', 'job', 'desk'
        )
        widgets = {
            'password': forms.PasswordInput(render_value=True),
        }

Here, I think it's too complex but i don't know how to reduce complexity.

views.py

class RegisterProfile(CreateView):
    model = MyUser
    template_name = 'fbLike/user_profile.html'
    form_class = StudentProfileForm
    second_form_class = EmployeeProfileForm

    def get_object(self):
        return super(RegisterProfile, self).get_object()

    def get_success_url(self):
        return reverse('login',)

    def get_context_data(self, **kwargs):
        context = super(RegisterProfile, self).get_context_data(**kwargs)
        if 'studenForm' not in context:
            context['studentForm'] = self.form_class
        if 'employeeForm' not in context:
            context['employeeForm'] = self.second_form_class
        return context


    def form_valid(self, form):
        self.object = MyUser.objects.create_user(**form)
        return super(RegisterProfile, self).form_valid(form)

    def form_invalid(self, form):
        return super(RegisterProfile, self).form_invalid(form)

    def post(self, request, *args, **kwargs):
        self.object = None
        if request.POST['profileType'] == 'student':
            form = self.form_class(request.POST, prefix='st')
            form.user_type = 'student'
        else:
            form = self.second_form_class(request.POST, prefix='em')
            form.user_type = 'employee'

        if form.is_valid():
            return self.form_valid(form)
        else:
            return self.form_invalid(form)


class UpdateProfile(UpdateView):
    model = MyUser
    template_name = 'fbLike/modify_profile.html'

    def get_form_class(self):
        if self.request.user.user_type == 'student':
            return StudentProfileForm
        return EmployeeProfileForm

    def get_success_url(self):
        return reverse('welcome',)

For CreateProfile, I would like to send 2 forms in my template and thanks to javscript, i can choose the correct form. But when i submit the form, i have an error with the user password. When i check my users in the admin page, the hashpassword system seems to fail. So, my first attempt to solve : it was to override save method in my form to use the request.user.set_password but it doesn't work.

I know it's a long explanation. But everyone could give me an example of CreateView class with 2 forms please ? If it isn't possible to use a class but a function how to implement this function ?

I thank you in advance

Community
  • 1
  • 1

1 Answers1

2

I will attempt to answer your multiple questions, but please in the future - one question per post. I realize you are new, but StackOverflow is not a discussion forum, have a look at the help section (link at the bottom of each page) specifically the section on asking questions.


I want to create multiple users with email authentication. [...] Do you think it's correct ? or should i create 1 class with common fields and 2 classes for distinct type of user ?

Its correct if it works with you. These kinds of questions are better suited for codereview.stackexchange.com. Especially as there isn't a bug.

But, how to proceed with multi-table inheritence ? is it possible ? how to know what is the type of user before check password if i have for example [...]

First, authentication backends are different topic altogether; they don't have anything to do with multiple table inheritance (I think you mean relationships or multiple user profiles).

You only need multiple authentication backends if your password authentication algorithm is different for each user type. If all user passwords are stored in the same location/system/database, then you don't need multiple authentication backends.

You should implement a method in your base user class, that returns the type of user. You can then use user_passes_check decorator to restrict access in your views.

This way you will achieve what I think is what you are after "this section only for students", etc.

Finally, with my models.py, backend.py i would try to use the CRUD system in my views.py to create and update users [..] Here, I think it's too complex but i don't know how to reduce complexity.

You don't need to repeat all the fields of your model in your ModelForm, you only need to override fields if you need to modify them, or to add extra "non-model" fields to the form. You can start with the simple ModelForm, like this:

class UserForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('name', 'email', 'password')

A common use of adding an additional field is to ask for password confirmation, by having an extra password field.

For CreateProfile, I would like to send 2 forms in my template and thanks to javscript, i can choose the correct form. But when i submit the form, i have an error with the user password. When i check my users in the admin page, the hashpassword system seems to fail. So, my first attempt to solve : it was to override save method in my form to use the request.user.set_password but it doesn't work.

Use a form wizard to implement a multi-step registration process. The first step, you ask the basic information and account type to be created; on the second step, you only send the relevant form instead of sending two forms and then switching them around using javascript.

I don't see anywhere in your code where you use set_password, so can't comment on the second part.

Community
  • 1
  • 1
Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284