Skip to content

Instantly share code, notes, and snippets.

@pankaj28843
Last active December 18, 2015 03:59
Show Gist options
  • Save pankaj28843/5722079 to your computer and use it in GitHub Desktop.
Save pankaj28843/5722079 to your computer and use it in GitHub Desktop.
So, you want to create users using django-tastypie
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password
from tastypie.authentication import (
Authentication, ApiKeyAuthentication, BasicAuthentication,
MultiAuthentication)
from tastypie.authorization import Authorization
from tastypie.resources import ModelResource
from tastypie import fields
from .models import UserProfile
from .utils import MINIMUM_PASSWORD_LENGTH, validate_password
from .exceptions import CustomBadRequest
class CreateUserResource(ModelResource):
user = fields.ForeignKey('core.api.UserResource', 'user', full=True)
class Meta:
allowed_methods = ['post']
always_return_data = True
authentication = Authentication()
authorization = Authorization()
queryset = UserProfile.objects.all()
resource_name = 'create_user'
always_return_data = True
def hydrate(self, bundle):
REQUIRED_USER_PROFILE_FIELDS = ("birth_year", "gender", "user")
for field in REQUIRED_USER_PROFILE_FIELDS:
if field not in bundle.data:
raise CustomBadRequest(
code="missing_key",
message="Must provide {missing_key} when creating a user."
.format(missing_key=field))
REQUIRED_USER_FIELDS = ("username", "email", "first_name", "last_name",
"raw_password")
for field in REQUIRED_USER_FIELDS:
if field not in bundle.data["user"]:
raise CustomBadRequest(
code="missing_key",
message="Must provide {missing_key} when creating a user."
.format(missing_key=field))
return bundle
def obj_create(self, bundle, **kwargs):
try:
email = bundle.data["user"]["email"]
username = bundle.data["user"]["username"]
if User.objects.filter(email=email):
raise CustomBadRequest(
code="duplicate_exception",
message="That email is already used.")
if User.objects.filter(username=username):
raise CustomBadRequest(
code="duplicate_exception",
message="That username is already used.")
except KeyError as missing_key:
raise CustomBadRequest(
code="missing_key",
message="Must provide {missing_key} when creating a user."
.format(missing_key=missing_key))
except User.DoesNotExist:
pass
# setting resource_name to `user_profile` here because we want
# resource_uri in response to be same as UserProfileResource resource
self._meta.resource_name = UserProfileResource._meta.resource_name
return super(CreateUserResource, self).obj_create(bundle, **kwargs)
class UserResource(ModelResource):
# We need to store raw password in a virtual field because hydrate method
# is called multiple times depending on if it's a POST/PUT/PATCH request
raw_password = fields.CharField(attribute=None, readonly=True, null=True,
blank=True)
class Meta:
# For authentication, allow both basic and api key so that the key
# can be grabbed, if needed.
authentication = MultiAuthentication(
BasicAuthentication(),
ApiKeyAuthentication())
authorization = Authorization()
# Because this can be updated nested under the UserProfile, it needed
# 'put'. No idea why, since patch is supposed to be able to handle
# partial updates.
allowed_methods = ['get', 'patch', 'put', ]
always_return_data = True
queryset = User.objects.all().select_related("api_key")
excludes = ['is_active', 'is_staff', 'is_superuser', 'date_joined',
'last_login']
def authorized_read_list(self, object_list, bundle):
return object_list.filter(id=bundle.request.user.id).select_related()
def hydrate(self, bundle):
if "raw_password" in bundle.data:
# Pop out raw_password and validate it
# This will prevent re-validation because hydrate is called
# multiple times
# https://github.com/toastdriven/django-tastypie/issues/603
# "Cannot resolve keyword 'raw_password' into field." won't occur
raw_password = bundle.data.pop["raw_password"]
# Validate password
if not validate_password(raw_password):
if len(raw_password) < MINIMUM_PASSWORD_LENGTH:
raise CustomBadRequest(
code="invalid_password",
message=(
"Your password should contain at least {length} "
"characters.".format(length=
MINIMUM_PASSWORD_LENGTH)))
raise CustomBadRequest(
code="invalid_password",
message=("Your password should contain at least one number"
", one uppercase letter, one special character,"
" and no spaces."))
bundle.data["password"] = make_password(raw_password)
return bundle
def dehydrate(self, bundle):
bundle.data['key'] = bundle.obj.api_key.key
try:
# Don't return `raw_password` in response.
del bundle.data["raw_password"]
except KeyError:
pass
return bundle
class UserProfileResource(ModelResource):
user = fields.ForeignKey(UserResource, 'user', full=True)
class Meta:
# For authentication, allow both basic and api key so that the key
# can be grabbed, if needed.
authentication = MultiAuthentication(
BasicAuthentication(),
ApiKeyAuthentication())
authorization = Authorization()
always_return_data = True
allowed_methods = ['get', 'patch', ]
detail_allowed_methods = ['get', 'patch', 'put']
queryset = UserProfile.objects.all()
resource_name = 'user_profile'
def authorized_read_list(self, object_list, bundle):
return object_list.filter(user=bundle.request.user).select_related()
## Since there is only one user profile object, call get_detail instead
def get_list(self, request, **kwargs):
kwargs["pk"] = request.user.profile.pk
return super(UserProfileResource, self).get_detail(request, **kwargs)
import json
from tastypie.exceptions import TastypieError
from tastypie.http import HttpBadRequest
class CustomBadRequest(TastypieError):
"""
This exception is used to interrupt the flow of processing to immediately
return a custom HttpResponse.
"""
def __init__(self, code="", message=""):
self._response = {
"error": {"code": code or "not_provided",
"message": message or "No error message was provided."}}
@property
def response(self):
return HttpBadRequest(
json.dumps(self._response),
content_type='application/json')
from django.utils.translation import ugettext as _
from django.contrib.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
"""
A model to store extra information for each user.
"""
user = models.OneToOneField(User, related_name='profile')
gender = models.CharField(_("gender"), max_length=10)
birth_year = models.PositiveIntegerField(_("birth year"))
def __unicode__(self):
return self.user.get_full_name()
#===========================================================================
# SIGNALS
#===========================================================================
def signals_import():
""" A note on signals.
The signals need to be imported early on so that they get registered
by the application. Putting the signals here makes sure of this since
the models package gets imported on the application startup.
"""
from tastypie.models import create_api_key
models.signals.post_save.connect(create_api_key, sender=User)
signals_import()
import re
MINIMUM_PASSWORD_LENGTH = 6
REGEX_VALID_PASSWORD = (
## Don't allow any spaces, e.g. '\t', '\n' or whitespace etc.
r'^(?!.*[\s])'
## Check for a digit
'((?=.*[\d])'
## Check for an uppercase letter
'(?=.*[A-Z])'
## check for special characters. Something which is not word, digit or
## space will be treated as special character
'(?=.*[^\w\d\s])).'
## Minimum 8 characters
'{' + str(MINIMUM_PASSWORD_LENGTH) + ',}$')
def validate_password(password):
if re.match(REGEX_VALID_PASSWORD, password):
return True
return False
@nkeilar
Copy link

nkeilar commented Aug 27, 2013

This has issues: if you put a new pk in the url then you can access profiles of any user in the system.

e.g.

http://127.0.0.1:8070/api/v1/user_profile/20/?format=json

@nkeilar
Copy link

nkeilar commented Aug 27, 2013

excludes = ['is_active', 'is_staff', 'is_superuser', 'date_joined', 'last_login']

should be

excludes = ['is_active', 'is_staff', 'is_superuser', 'date_joined', 'last_login', 'password']

@nkeilar
Copy link

nkeilar commented Aug 27, 2013

Also the regex doesn't seem to work for a password like '1234abcd#'

@pankaj28843
Copy link
Author

@madteckhead
Sorry for late reply.

Your first 3 comments

I have published an example project for user registration on https://github.com/psjinx/django-tastypie-custom-user-example. Please have a look at this and let me know if you find any bugs or have suggestions.

You can mail me at {my nick name}@gmail

Also the regex doesn't seem to work for a password like '1234abcd#'

Regex cheks for uppercase character, which is not present in above string.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment