CraigH
CraigH

Reputation: 2061

Django Rest Framework User Registrations with extra fields

I'm trying to use DRF to allow users to create a new user account via my API. I have a few requirements that might be different than the norm

  1. On successful creation it needs to return a user token using DRF's token feature
  2. All POST'd fields need to be validated
  3. I want to be able to POST the users phone number which will be stored in a profile model.
  4. First name, last name, Email address and Phone number need to be required fields

So far I came up with this, but I am getting an error message saying "Column 'user_id' cannot be null"

View

@api_view(['POST'])
@permission_classes((AllowAny,))
def register_user(request):
    serialized = UserSerializer(data=request.DATA)
    if serialized.is_valid():
        user = User.objects.create_user(
            email = serialized.init_data['email'],
            username = serialized.init_data['username'],
            password = serialized.init_data['password'],
            first_name = serialized.init_data['first_name'],
            last_name = serialized.init_data['last_name']
        )

        phone_number = request.DATA["phone_number"]  
        #save phone_number to profile model here

        return Response(serialized.data['token'], status=status.HTTP_201_CREATED)
    else:
        return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)

Serializer

class UserSerializer(serializers.ModelSerializer):
    token = serializers.SerializerMethodField('get_token')

    class Meta:
        model = User
        fields = ('password', 'username', 'first_name', 'last_name', 'email', 'token', 'phone_number')


    def get_token(self, obj):
        token = Token.objects.create(user=obj)
        return token.key

My questions are

  1. How do i get this working ?
  2. Do i need separate validation on the phone number, first_name, last_name, email to make sure they are required or is that handled by the model?
  3. Is this the correct way to handle the POST of the phone number?
  4. Is this secure ? If not, what can I do to make it more secure?

Stacktrace

Django Version: 1.6.8
Python Version: 2.7.5
Installed Applications:
('django.contrib.sites',
 'django.contrib.admin',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'my_app',
 'allauth',
 'allauth.account',
 'allauth.socialaccount',
 'allauth.socialaccount.providers.facebook',
 'debug_toolbar',
 'rest_framework',
 'rest_framework.authtoken',
 'django_extensions')
Installed Middleware:
('django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'debug_toolbar.middleware.DebugToolbarMiddleware')


Traceback:
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
  112.                     response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/views/decorators/csrf.py" in wrapped_view
  57.         return view_func(*args, **kwargs)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/views/generic/base.py" in view
  69.             return self.dispatch(request, *args, **kwargs)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  403.             response = self.handle_exception(exc)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/rest_framework/views.py" in dispatch
  400.             response = handler(request, *args, **kwargs)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/rest_framework/decorators.py" in handler
  50.             return func(*args, **kwargs)
File "/home/vagrant/projects/my_project/my/my_app/api/views.py" in register_user
  60.         return Response(serialized.data['token'], status=status.HTTP_201_CREATED)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/rest_framework/serializers.py" in data
  572.                 self._data = self.to_native(obj)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/rest_framework/serializers.py" in to_native
  351.             value = field.field_to_native(obj, field_name)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/rest_framework/fields.py" in field_to_native
  1041.         value = getattr(self.parent, self.method_name)(obj)
File "/home/vagrant/projects/my_project/my/my_app/api/serializers.py" in get_token
  19.         token = Token.objects.create(user=obj)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/manager.py" in create
  157.         return self.get_queryset().create(**kwargs)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/query.py" in create
  322.         obj.save(force_insert=True, using=self.db)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/rest_framework/authtoken/models.py" in save
  33.         return super(Token, self).save(*args, **kwargs)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/base.py" in save
  545.                        force_update=force_update, update_fields=update_fields)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/base.py" in save_base
  573.             updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/base.py" in _save_table
  654.             result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/base.py" in _do_insert
  687.                                using=using, raw=raw)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/manager.py" in _insert
  232.         return insert_query(self.model, objs, fields, **kwargs)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/query.py" in insert_query
  1514.     return query.get_compiler(using=using).execute_sql(return_id)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/models/sql/compiler.py" in execute_sql
  903.             cursor.execute(sql, params)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/debug_toolbar/panels/sql/tracking.py" in execute
  174.         return self._record(self.cursor.execute, sql, params)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/debug_toolbar/panels/sql/tracking.py" in _record
  104.             return method(sql, params)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/backends/util.py" in execute
  69.             return super(CursorDebugWrapper, self).execute(sql, params)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/backends/util.py" in execute
  53.                 return self.cursor.execute(sql, params)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/backends/mysql/base.py" in execute
  129.                 six.reraise(utils.IntegrityError, utils.IntegrityError(*tuple(e.args)), sys.exc_info()[2])
File "/home/vagrant/envs/my/lib/python2.7/site-packages/django/db/backends/mysql/base.py" in execute
  124.             return self.cursor.execute(query, args)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/MySQLdb/cursors.py" in execute
  205.             self.errorhandler(self, exc, value)
File "/home/vagrant/envs/my/lib/python2.7/site-packages/MySQLdb/connections.py" in defaulterrorhandler
  36.     raise errorclass, errorvalue

Exception Type: IntegrityError at /api/user/register/
Exception Value: (1048, "Column 'user_id' cannot be null")

Upvotes: 3

Views: 2772

Answers (1)

Kevin Brown-Silva
Kevin Brown-Silva

Reputation: 41699

I would recommend using class-based views with Django REST Framework. They give you a lot of extra power that is missing from function-based views, and they are more supported. While this is less important for this case, this is also a very basic situation that avoids most of what Django REST Framework brings to the table.

Do i need separate validation on the phone number, first_name, last_name, email to make sure they are required or is that handled by the model?

When you call serializer.is_valid, it should check to make sure that any fields that are specified as required on the model are included. Try it out, and if it isn't happening make sure that you don't have empty=False specified on any of the fields. If you aren't in the position to fix that, you can override the field on the serializer and set required=True there.

Is this the correct way to handle the POST of the phone number?

The serializer can create the user automatically by just calling serializer.save(). If you have custom logic in the create_user manager that you need to use, then you probably can't use save.

Is this secure ? If not, what can I do to make it more secure?

Django will help you out a lot here, so your code currently looks pretty well. One thing you may want to consider is that the init_data from the serializer is the same as that from request.DATA. You should probably access the validated data from serializer.object (like serializer.object.username instead, as any validators may have modified your data to make it more friendly to the Django ORM.

Upvotes: 2

Related Questions