I want to register a new user with a associated list of categories.
I have a table called categories with list of unique categories (category id, category name).
I have a table called accounts with a list of users (username, email, password and list of associated categories).
I have a table called account_categories which is a mapping between users and categories.
Unfortunately when I am trying to create new user I receive an error that provided categories already exists. I don't want to update categories table. I want to update accounts and account_categories.
What am I doing wrong?
model.py
class Category(models.Model):
categoryname = models.CharField(unique=True, max_length=50)
def __str__(self):
return "{0}".format(self.categoryname)
class AccountManager(BaseUserManager):
def create_user(self, email, age, categories, password=None, **kwargs):
if not email:
raise ValueError('Users must have a valid email address.')
if not kwargs.get('username'):
raise ValueError('Users must have a valid username.')
if not age:
raise ValueError('Users must have a valid age.')
if not categories:
raise ValueError('Users must choose at least one intrest.')
account = self.model(
email=self.normalize_email(email), age=age, categories=categories, username=kwargs.get('username')
)
account.set_password(password)
account.save()
return account
def create_superuser(self, email, age, categories, password, **kwargs):
account = self.create_user(email=email, age=age, categories=categories, password=password, **kwargs)
account.is_admin = True
account.save()
return account
class Account(AbstractBaseUser):
email = models.EmailField(unique=True)
username = models.CharField(unique=True, max_length=25)
age = models.SmallIntegerField()
categories = models.ManyToManyField(Category)
objects = AccountManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['age', 'email']
def __unicode__(self):
return self.email
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'categoryname')
class AccountSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=False)
confirm_password = serializers.CharField(write_only=True, required=False)
categories = CategorySerializer(many=True)
class Meta:
model = Account
fields = ('id', 'email', 'username', 'age', 'categories', 'password', 'confirm_password',)
def create(self, validated_data):
return Account.objects.create(**validated_data)
views.py
class AccountViewSet(viewsets.ModelViewSet):
lookup_field = 'username'
queryset = Account.objects.all()
serializer_class = AccountSerializer
def get_permissions(self):
if self.request.method in permissions.SAFE_METHODS:
return (permissions.AllowAny(),)
if self.request.method == 'POST':
return (permissions.AllowAny(),)
return (permissions.IsAuthenticated(), IsAccountOwner(),)
def create(self, request):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
Account.objects.create_user(**serializer.validated_data)
return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
return Response({
'status': 'Bad request',
'message': 'Account could not be created with received data. Probably this email / username has already been used.' + str(serializer.errors) + str(request.data) #+ str(serializer.is_valid())
}, status=status.HTTP_400_BAD_REQUEST)
database scheme
http://postimg.org/image/o15ybcf9f/
error
http://postimg.org/image/4i5muo8fz/
Related
Below are listed my Models, View, and Serializer classes. I am making an admin dashboard where admin can create users, list them, and search a user by ID.
Users are being created successfully but the GET request for listing and searching returns an empty list even when there are users present in the Database.
My Model Classes:
class MyAccountManager(BaseUserManager):
def create_user(self,email,username,first_name,last_name, age, password=None):
if not email:
raise ValueError("Users must have email address")
if not username:
raise ValueError("Users must have username")
user = self.model(
email=self.normalize_email(email),
username=username,
first_name=first_name,
last_name=last_name,
age=age,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self,username,email,first_name,last_name,age,password):
user = self.create_user(
email=self.normalize_email(email),
username=username,
first_name=first_name,
last_name=last_name,
age=age,)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser):
email = models.EmailField(verbose_name="email",max_length=60,unique=True)
username = models.CharField(max_length=30,unique=True)
date_joined = models.DateTimeField(verbose_name='date joined',auto_now_add=True)
last_login = models.DateTimeField(verbose_name='last login',auto_now=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
first_name = models.CharField (max_length=60)
last_name = models.CharField(max_length=60)
age = models.DateField(default=datetime.date.today)
profile_pic = models.ImageField(upload_to='image',blank=True,null=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username','first_name','last_name','age']
objects = MyAccountManager()
def __str__(self):
return self.username + "," +self.email
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
My Serializer Class:
class UserSerializer (serializers.ModelSerializer):
class Meta:
model = User
fields = ['username','first_name','last_name','email','age','is_active']
read_only_fields = ('email',)
#fields = '__all__'
My View Class:
class UserList(generics.ListCreateAPIView):
serializer_class = UserSerializer
queryset = User.objects.all()
permission_classes = [permissions.IsAdminUser]
#authentication_classes = [authentication.TokenAuthentication]
def list(self,request):
queryset = self.get_queryset()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
I am sending a GET request to list all the users present but it is returning an empty list ([]).
There is no error but the return is an empty list.
However, I am creating users and they are being stored in Database.
I need to check if a user exists in the database given their input: either their email or phone number. If the user exists, the user is used to take their order. If the user inputs both their email and phone and they don't exist in the database, I need to create the user. How do I do this?
This is the serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
extra_kwargs = {
'email': {'required': True},
'phone': {'required': True}
}
def create(self, validated_data):
user= User.objects.create(
email=validated_data['email'],
phone=validated_data['phone']
)
user.save()
return user
class OrderSerializer(serializers.ModelSerializer):
food= FoodSerializer(many=True)
class Meta:
model = Order
fields = ('date','food','phone','email')
def validate(self, data):
email = data.get('email')
phone= data.get('phone')
if not email and not phone:
raise serializers.ValidationError("Email or phone required")
return data
def checkuser(self, cliente, data):
if data['email'] or data['phone'] in user:
return user
elif data['email'] or data['phone'] not in user:
pass
def create(self, validated_data):
order = Order.objects.create(
email=validated_data['email'],
phone=validated_data['phone'],
date= validated_data['date'],
food= validated_data['food'],
quantity= validated_data['quantity'],
)
order.save()
return order
get_or_create() helper is what you want.
def create(self, validated_data):
filters = {}
if email := validated_data.get('email'):
filters["email"] = email
if phone := validated_data.get('phone'):
filters["phone"] = phone
user, _ = User.objects.get_or_create(**filters)
return user
I have a wishlist app with a custom user model. I added the user as the foreign key to each wishlist item. However, I am only able to add the foreign key by specifying a default user, even after deleting the existing db files and running makemigrations and migrate. The default user is associated with the wishlist item regardless of which user is logged in, and if I don't include a default user I get the "NOT NULL constraint failed: wishlist_wish.user_id" error.
How can I update the code so that the wishlist item is associated with the logged-in user creating the item?
A related question is whether I need to include a user id in my custom user model? I tried doing this as well and ran into the same problem. Thanks in advance.
Wish Model:
class Wish(models.Model):
name = models.TextField()
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, default=1)
def __str__(self):
return self.name
Wish View:
def add_wish(request):
user = request.user
wishes = Wish.objects.filter(user=request.user)
if request.method == 'POST':
form = WishForm(request.POST)
if form.is_valid():
form.save()
return redirect('home')
else:
form = WishForm()
context = {'form' : form, 'wishes' : wishes}
return render(request, 'wishlist/add_wish.html', context)
Wish Form:
class WishForm(ModelForm):
class Meta:
model = Wish
fields = ('name', )
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['name'].widget.attrs.update({'class' : 'textarea', 'placeholder' : 'Enter wishlist item'})
Custom User (Account) Model:
class MyAccountManager(BaseUserManager):
def create_user(self, email, username, first_name, last_name, password=None):
if not email:
raise ValueError("Users must provide an email to create an account.")
if not first_name:
raise ValueError("Users must provide full name to create an account.")
if not last_name:
raise ValueError("Users must provide full name to create an account.")
user = self.model(
email = self.normalize_email(email),
username = username,
first_name = first_name,
last_name = last_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, username, first_name, last_name, password):
user = self.create_user(
email = self.normalize_email(email),
username = username,
first_name = first_name,
last_name = last_name,
password = password
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
class Account(AbstractBaseUser):
email = models.EmailField(max_length=100, unique=True)
username = models.CharField(max_length=100, unique=False, blank=True, null=True)
date_joined = models.DateTimeField(auto_now_add=True)
last_login = models.DateTimeField(auto_now=True)
is_admin = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
objects = MyAccountManager()
def __str__(self):
return self.first_name + " " + self.last_name
def has_perm(self, perm, obj=None):
return self.is_admin
def has_module_perms(self, app_label):
return True
You simply need to provide a value for the .user for the Wish you are creating:
from django.contrib.auth.decorators import login_required
#login_required
def add_wish(request):
wishes = Wish.objects.filter(user=request.user)
if request.method == 'POST':
form = WishForm(request.POST)
if form.is_valid():
form.instance.user = requst.user
form.save()
return redirect('home')
else:
form = WishForm()
context = {'form' : form, 'wishes' : wishes}
return render(request, 'wishlist/add_wish.html', context)
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
The goal is to test creating a new custom user using APITestCase.
In models.py got two new classes inheriting from AbstractBaseUser and BaseUserManager, respectively
class MyUser(AbstractBaseUser):
objects = MyUserManager()
class Meta:
db_table = 'user_entity'
user_id = models.AutoField(primary_key=True)
#username = models.CharField(max_length=USERNAME_MAX_LENGTH, unique=True, validators=[validators.validate_username])
username = models.CharField(max_length=20, unique=True)
password = models.CharField(max_length=256)
first_name = models.CharField(max_length=25)
last_name = models.CharField(max_length=25)
email = models.EmailField(verbose_name='email', max_length=100, unique=True)
last_access = models.DateField(default=datetime.date.today)
creation_date = models.DateTimeField(default=timezone.now)
last_update = models.DateField(default=datetime.date.today)
user_type = models.IntegerField()
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = ['username', 'first_name', 'last_name', 'user_type']
def __str__(self):
return str(self.user_id) + " (%s)" % str(self.email)
def has_perm(self, perm, obj=None):
return self.user_type == 0
def has_module_perms(self, app_label):
return True
and
class MyUserManager(BaseUserManager):
def _create_generic_user(self, email, username, password, first_name, last_name, user_type):
if not username:
raise ValueError("Username cannot be empty")
if not email:
raise ValueError("Email cannot be empty")
if not first_name:
raise ValueError("First name cannot be empty")
if not last_name:
raise ValueError("Last name cannot be empty")
if not password:
raise ValueError("Password cannot be empty")
user = self.model(
username=username,
first_name=first_name,
last_name=last_name,
email=self.normalize_email(email),
user_type=user_type,
is_staff=user_type == 0,
is_active=False
)
user.set_password(password)
user.save(user=self._db)
return user
def create_user(self, email, username, password, first_name, last_name):
return self._create_generic_user(email, username, password, first_name, last_name, 2)
def create_admin(self, email, username, password, first_name, last_name):
return self._create_generic_user(email, username, password, first_name, last_name, 0)
Then, in serializers.py created a MyUserSerializer for DRF inheriting from a ModelSerializer
class MyUserSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True, # make sure email is provided
validators=[UniqueValidator(queryset=MyUser.objects.all())] # make sure email is unique
)
username = serializers.CharField(
required=True,
validators=[UniqueValidator(queryset=MyUser.objects.all())],
min_length=5,
max_length=20
)
password = serializers.CharField(
write_only=True,
required=True,
max_length=256
)
first_name = serializers.CharField(
required=True,
max_length=25
)
last_name = serializers.CharField(
required=True,
max_length=25
)
def create(self, validated_data):
user = MyUser.objects.create_user(validated_data['email'], validated_data['username'], validated_data['password'],
validated_data['first_name'], validated_data['last_name'])
return user
class Meta:
model = MyUser
fields = ('user_id', 'email', 'username', 'password', 'first_name', 'last_name')
a view with APIView
class MyUserCreate(APIView):
"""
Creates the user.
"""
def post(self, request, format='json'):
serializer = MyUserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
if user:
return Response(serializer.data, status=status.HTTP_201_CREATED)
and urls
urlpatterns = [
url(r'api/users^$', views.MyUserCreate.as_view(), name='user-create'),
]
From this, then went on to create a simple test of creating users
class MyUserTest(APITestCase):
def setUp(self):
# Create a user
self.test_user = MyUser.objects.create_user('tiagomartinsperes#gmail.com', 'tiagoperes', 'EWFdfew45te!sadf32', 'Tiago', 'Peres')
# URL for creating user
self.create_url = reverse('user-create')
def test_create_user(self):
"""
Ensure we can create a new user and a valid token is created with it.
"""
data = {
'email': 'cnf32344#zzrgg.com',
'username': 'andresantos',
'password': 'test',
'first_name': 'André',
'last_name': 'Santos'
}
response = self.client.post(self.create_url , data, format='json')
# Make sure we have two users in the database
self.assertEqual(MyUser.objects.count(), 2)
# Return 201
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# Return the username and email upon successful creation
self.assertEqual(response.data['email'], data['email'])
self.assertEqual(response.data['username'], data['username'])
self.assertFalse('password' in response.data)
self.assertEqual(response.data['first_name'], data['first_name'])
self.assertEqual(response.data['last_name'], data['last_name'])
Thing is, running
python manage.py test
gets the following error:
Creating test database for alias 'default'... System check identified
no issues (0 silenced). E
====================================================================== ERROR: test_create_user (user.tests.MyUserTest) Ensure we can create a
new user and a valid token is created with it.
---------------------------------------------------------------------- Traceback (most recent call last): File
"C:\Users\tiago\Desktop\letsgo\COVID19-be\django_server\user\tests.py",
line 10, in setUp
self.test_user = MyUser.objects.create_user('tiagomartinsperes#gmail.com',
'tiagoperes', 'EWFdfew45te!sadf32', 'Tiago', 'Peres') File
"C:\Users\tiago\Desktop\letsgo\COVID19-be\django_server\user\models.py",
line 38, in create_user
return self._create_generic_user(email, username, password, first_name, last_name, 2) File
"C:\Users\tiago\Desktop\letsgo\COVID19-be\django_server\user\models.py",
line 34, in _create_generic_user
user.save(user=self._db) File "C:\Users\tiago\Desktop\letsgo\unstable\lib\site-packages\django\contrib\auth\base_user.py",
line 66, in save
super().save(*args, **kwargs) TypeError: save() got an unexpected keyword argument 'user'
---------------------------------------------------------------------- Ran 1 test in 0.172s
FAILED (errors=1) Destroying test database for alias 'default'...
and no users are being created. What can I do to solve it?
The issue is at here
user.save(user=self._db)
The django Model save has the following signature.
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
Since you are passing a database as value(self._db), I think you are expected to write
user.save(using=self._db)
So I want to create a custom message for the different fail based on the serializer validation. But I was only able to find how to create a standard custom response for success and fail only. Though the only response I want to create is for the failed prompt message.
I found one of the posts but it's not really what I wanted for the error code
source: Django Rest Framework custom response message
Basically, the error message must be based on the specific validation error in serializer.py
Here is my code
serializer.py
class UserCreate2Serializer(ModelSerializer):
email = EmailField(label='Email Address')
valid_time_formats = ['%H:%M', '%I:%M%p', '%I:%M %p']
birthTime = serializers.TimeField(format='%I:%M %p', input_formats=valid_time_formats, allow_null=True, required=False)
class Meta:
model = MyUser
fields = ['username', 'password', 'email', 'first_name', 'last_name', 'gender', 'nric', 'birthday', 'birthTime', 'ethnicGroup']
extra_kwargs = {"password": {"write_only": True}}
def validate(self, data): # to validate if the user have been used
email = data['email']
user_queryset = MyUser.objects.filter(email=email)
if user_queryset.exists():
raise ValidationError("This user has already registered.")
return data
# Custom create function
def create(self, validated_data):
username = validated_data['username']
password = validated_data['password']
email = validated_data['email']
first_name = validated_data['first_name']
last_name = validated_data['last_name']
gender = validated_data['gender']
nric = validated_data['nric']
birthday = validated_data['birthday']
birthTime = validated_data['birthTime']
ethnicGroup = validated_data['ethnicGroup']
user_obj = MyUser(
username = username,
email = email,
first_name = first_name,
last_name = last_name,
gender = gender,
nric = nric,
birthday = birthday,
birthTime = birthTime,
ethnicGroup = ethnicGroup,
)
user_obj.set_password(password)
user_obj.save()
return validated_data
views.py
class CreateUser2View(CreateAPIView):
permission_classes = [AllowAny]
serializer_class = UserCreate2Serializer
queryset = MyUser.objects.all()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if not serializer.is_valid(raise_exception=False):
return Response({"promptmsg": "You have failed to register an account"}, status=status.HTTP_400_BAD_REQUEST)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response({'promptmsg': 'You have successfully register'}, status=status.HTTP_201_CREATED, headers=headers)
As you can see from serializer validate section, if email exists, it will give an error message of this user has been registered. Same for username but Django has its own validation for it since it is built in.
Is there a way for the error message to be like this:
if username is taken, instead of this
"username": [
"A user with that username already exists."
]
to this
"promptmsg": [
"A user with that username already exists."
]
--updated--
models.py
class MyUser(AbstractUser):
userId = models.AutoField(primary_key=True)
gender = models.CharField(max_length=6, blank=True, null=True)
nric = models.CharField(max_length=9, blank=True, null=True)
birthday = models.DateField(blank=True, null=True)
birthTime = models.TimeField(blank=True, null=True)
ethnicGroup = models.CharField(max_length=30, blank=True, null=True)
def __str__(self):
return self.username
You can get this list of errors at serializer.errors look how validation works