I'm trying to create a profile in the DRF create function in serializer, but when save User model the next exception is triggered
ValueError: "<Usuario: Some Name>" needs to have a value for field "id" before this many-to-many relationship can be used.
This is my configuration background over python 3
Django == 1.11
DRF == Django rest framework
class CustomUserManager(BaseUserManager):
def _create_user(self, firstname, lastname, password, **extra_fields):
now = timezone.now()
if not firstname:
raise ValueError(u'The firstname is required.')
user = self.model(
firstname=firstname,
lastname=lastname,
last_login=now,
**extra_fields
)
user.set_password(password)
user.save()
return user
class Usuario(
AbstractBaseUser, PermissionsMixin,
TimeStampedModel, SoftDeletableModel
):
objects = CustomUserManager()
class Profile(models.Model):
user = models.OneToOneField(Usuario, related_name='profile', on_delete=models.CASCADE)
class UserSerializer(serializers.ModelSerializer):
profile = PerfilSerializer(read_only=True)
def create(self, validate_data):
user_data = validate_data
profile_data = validate_data.pop('profile')
usr = Usuario(**user_data)
usr.save()
profl = Profile(**profile_data)
profl.save()
profl.user.set(usr)
return usr
I want to get the model with the user instance and the profile created
You are on the right track, just tweak your create on the profile serializer. Try this:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
def create(self, validated_data):
user = User.objects.create(**validated_data)
Profile.objects.create(user=user)
return user
Simply use Django Signals on User save.
from django.contrib.auth.models import User
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User)
def save_profile(sender, instance, **kwargs):
instance.profile.save()
Check out this concrete tutorial, as it solves your problem.
https://simpleisbetterthancomplex.com/tutorial/2016/07/28/how-to-create-django-signals.html
Glad to help you!
Related
I have a custom user model, I would like my CustomUser model to have a OneToOne relationship with the Person model; Since I want to first register persons and then create a username for them, since not all registered people need to have a username.
I have tried the code shown below; but what I get is the following error:
Cannot assign "1": "CustomUser.dni" must be a "Person" instance.
But, the ID number is already registered in the Person table
P.S. If anyone has a better suggestion for getting people to register first and then create a username for them when strictly necessary, I'd really appreciate it.
models.py
from .managers import CustomUserManager
# MODEL PERSON
class Person(models.Model):
dni = models.CharField('Número de DNI', max_length=8, unique=True)
...
# MODEL CUSTOMUSER
class CustomUser(AbstractBaseUser, PermissionsMixin):
dni = models.OneToOneField(Person, on_delete=models.CASCADE, unique=True)
email = models.EmailField('Correo electrónico', max_length=355, unique=True)
is_staff = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
objects = CustomUserManager()
USERNAME_FIELD = 'dni'
REQUIRED_FIELDS = ['email']
...
managers.py
from django.contrib.auth.models import BaseUserManager
class CustomUserManager(BaseUserManager):
def create_user(self, dni, email, password=None):
if not dni:
raise ValueError('Debe ingresar el número de DNI.')
person = self.model(
dni=dni,
email=self.normalize_email(email),
)
person.set_password(password)
person.save(using=self._db)
return person
def create_superuser(self, dni, email, password=None):
person = self.create_user(
dni,
password=password,
email=email,
)
person.is_admin = True
person.is_superuser = True
person.save(using=self._db)
return person
settings.py
AUTH_USER_MODEL = 'accounts.CustomUser'
views.py
from accounts.forms import RegisterForm
class RegisterView(View):
form_class = RegisterForm
initial = {'key': 'value'}
template_name = 'accounts/register.html'
def get(self, request, *args, **kwargs):
form = self.form_class(initial=self.initial)
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
if form.is_valid():
form.save()
username = form.cleaned_data.get('username')
messages.success(request, f'Cuenta creada para exitosamente.')
return redirect(to='/')
return render(request, self.template_name, {'form': form})
I want to register a person in the Person table.
After creating the person, you should only be able to create their username and password, from the administration site; that is, an external person cannot register on the site, because the site will have many people in the database, but only some of them will be users of the system.
I hope this image helps clarify the idea.
Process flow
What you're probably looking to do is subclass the django registration form. Then you can change the fields that are required.
I believe the password field would be required by default so a NULL value would not be valid. However, you could quite easily set the password as a randomly generated UUID and then manually change it in the admin panel.
I am trying to create a new user from the modelform by calling the CustomerUserManager in managers.py
class CustomUserCreationForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(CustomUserCreationForm,self).__init__(*args, **kwargs)
try:
if args[0]["email"]:
self.email= args[0]["email"]
self.password= args[0]["password1"]
except:
pass
class Meta:
model = CustomUser
fields = "__all__"
def save(self, **kwargs):
return CustomUserManager.create_user(self,email=self.email,password=self.password,name="hic")
The managers.py is
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, name,**extra_fields):
...
user = self.model(email=email,name=name, stripe_customer_id=customer.id ,**extra_fields)
user.set_password(password)
user.save()
return user
Obviously the self.model does not exist, so how can I correctly create that user from the modelform? If I import the model, I get circular error import.
here is my model.py
class CustomUser(AbstractUser):
username = None
email = models.EmailField(_('email address'), unique=True)
name = models.CharField(verbose_name=_("first name"), max_length=50)
stripe_customer_id = models.CharField(max_length=120)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name']
objects = CustomUserManager()
I'm not sure why you would want a save function in your form, rather than in your view using the model's save() function after your form data has validated...
That said, if you do want a create_user function in your manager, you would call it like
from django.contrib.auth import get_user_model
get_user_model().objects.create_user(...)
This should ensure the model is present to be referred to.
I'm starting to learn Django and have a class called Customer in my models.
class Customer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,
primary_key=True)
cart = models.ManyToManyField(Product)
orders = models.ManyToManyField(Order)
def __init__(self, user):
self.user = user
I'm importing django.contrib.auth to register users to the database, but I would like to also initialize a Customer object upon registration.
I first attempted to override the save() method from the UserCreationForm and initialize a Customer object there:
class UserCreationForm(forms.ModelForm):
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
user.set_password(self.cleaned_data["password1"])
customer = Customer(user)
customer.save()
if commit:
user.save()
return user
But it did not seem to create a Customer object.
Alternatively, is it better to extend the User class to have the Customer class fields? I initially thought I should keep authentication separate, which is why I created the Customer class.
Might be better if you created a signal instead!
from django.db.models import signals
from django.dispatch import receiver
from django.contrib.auth.models import User
from path.to.models import Customer
#receiver(signals.post_save, sender = User)
def create_customer(sender, instance, created, *args, **kwargs):
if created:
c = Customer(...) #create your customer object
c.save()
and in apps.py, import signals to run it.
I have a custom user model and a user manager defined as follows:
/accounts/models.py
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin
)
from django.db import models
from django.utils import timezone
class UserManager(BaseUserManager):
def create_user(self, email, first_name, last_name, username=None, password=None):
if not email:
raise ValueError("Users must have a valid email address")
if not first_name and last_name:
raise ValueError("Users must have a first and last name")
created_username = ''.join([first_name.lower(), last_name[:1].lower()])
i=2
while User.objects.filter(username=created_username).exists():
created_username = ''.join([first_name.lower(), last_name[:i].lower()])
i+=1
user = self.model(
email=self.normalize_email(email),
first_name=first_name,
last_name=last_name,
username=created_username
)
user.set_password(password)
user.save()
return user
def create_superuser(self, email, first_name, last_name, password):
user = self.create_user(
email,
first_name,
last_name,
password
)
user.is_staff = True
user.is_admin = True
user.is_superuser = True
user.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
first_name = models.CharField(max_length=40, blank=True)
last_name = models.CharField(max_length=40, blank=True)
username = models.CharField(max_length=40, unique=True, blank=True, editable=False)
# display_name = models.CharField(max_length=150)
bio = models.TextField(blank=True, null=True)
avatar = models.ImageField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_admin = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['first_name','last_name']
def __str__(self):
return "{} #{}".format(self.email, self.username)
def get_short_name(self):
return self.first_name
def get_full_name(self):
return ' '.join([self.first_name, self.last_name])
This seems to work perfectly when registering a superuser from the shell. I have a form and a view set up to register regular users on my site as follows:
/accounts/forms.py
from django import forms
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
auth_code = 'hamburger'
def validate_authorization(value):
if value != auth_code:
raise ValidationError(
_('Must have valid authorization code in order to register.')
)
class UserCreateForm(UserCreationForm):
authorization_code = forms.CharField(max_length=10, required=True, validators=[validate_authorization])
class Meta:
model = get_user_model()
fields = ("email", "first_name", "last_name", "password1", "password2", "authorization_code")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["email"].label = "Email Address"
self.fields["first_name"].label = "First Name"
self.fields["last_name"].label = "Last Name"
self.fields["password1"].label = "Password"
self.fields["password2"].label = "Password Confirmation"
self.fields["authorization_code"].label = "Authorization Code"
/accounts/views.py
from django.shortcuts import render
from django.template import RequestContext
from django.contrib.auth import login, logout
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.urlresolvers import reverse_lazy
from django.views import generic
from django.http import HttpResponseRedirect
from django.contrib.auth import get_user_model
from . import forms
class SigninView(generic.FormView):
form_class = AuthenticationForm
success_url = '/dashboard/' #reverse_lazy('index')
template_name = 'accounts/signin.html'
def get_form(self, form_class=None):
if form_class is None:
form_class = self.get_form_class()
return form_class(self.request, **self.get_form_kwargs())
def form_valid(self, form):
login(self.request, form.get_user())
return super().form_valid(form)
class SignoutView(generic.RedirectView):
url = '/' #reverse_lazy("home")
def get(self, request, *args, **kwargs):
logout(request)
return super().get(request, *args, **kwargs)
class RegisterView(generic.CreateView):
form_class = forms.UserCreateForm
success_url = '/'
template_name = 'accounts/register.html'
def form_valid(self, form):
self.object = form.save(commit=False)
form.instance.username = ''.join([form.instance.first_name.lower(), form.instance.last_name[:1].lower()])
i=2
while get_user_model().objects.filter(username=form.instance.username).exists():
form.instance.username = ''.join([form.instance.first_name.lower(), form.instance.last_name[:i].lower()])
i+=1
form.save()
return HttpResponseRedirect(self.get_success_url())
# return super(RegisterView, self).form_valid(form)
I am at a loss as to why my superuser cannot log into the website but my regular users can. Also you will notice I have a while statement that auto generates a username based on the entered first and last name. Initially I had this only in the UserManager however, the form was bypassing the user manager and so I had to add the same block of code to my view. So there seems to be a disconnect between users created from the form versus users created from the shell (UserManager).
The authorization_code is in place because I don't want just anybody to be able to register on my site and I didn't know a better way. I am open to better suggestions.
Additional information that may be helpful
settings.py
# Set user authentication model
AUTH_USER_MODEL = 'accounts.User'
Python 3.5, Django 1.10
Thank you in advance for any advice or insight.
Problem solved.
In
def create_superuser(self, email, first_name, last_name, password):
user = self.create_user(
email,
first_name,
last_name,
password
)
I was forgetting to set password=password,. From looking at the password field in the database, it seems this was also resulting in (as close as I can tell) bypassing <algorithm>$<iterations>$<salt> (per the Django docs https://docs.djangoproject.com/en/1.10/topics/auth/passwords/) though the password was still being hashed in some way (not being stored in plain text) the password field for superusers was considerably shorter than the password field for normal users. Whatever it was doing, it was not storing the actual password and was giving me an invalid username/password when attempting to log in with a superuser account.
So the proper way is
def create_superuser(self, email, first_name, last_name, password):
user = self.create_user(
email,
first_name,
last_name,
password=password,
)
I still don't understand why created_username is being bypassed in the UserManager when saving a user from the AuthenticationForm but I found a workaround by adding the same while statement to the view. At least all is functional now. I'm still interested to learn if anybody has further insight into this matter.
I have a basic Django ModelForm to create a new user:
class UserCreationForm(forms.ModelForm):
class Meta:
model = User
# stuff here
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
if commit:
user.save()
return user
I also have a custom UserManager for handling the User creation function, which also creates a UserProfile and attaches it to the User:
class UserManager(BaseUserManager):
def create_user(self, email, password=None):
"""Create a standard user."""
email = self.normalize_email(email)
user = self.model(email=email)
user.set_password(password)
user.save()
profile = UserProfile()
profile.user = user
profile.save()
return user
class User(AbstractBaseUser, PermissionsMixin):
objects = UserManager()
However, whenever the UserCreationForm is called successfully, it creates the User, but doesn't create the UserProfile.
My question is, why is the UserCreationForm bypassing my custom UserManager? Is there some special syntax I need to give the form to tell it my model has a custom manager?
I am also using django.views.generic.CreateView as the view, so I guess I could change the post method manually to create a User and UserProfile, but I'd like to know why this is failing before coming up with some hacky fix considering it seems like a very basic operation.
Thanks