-
-
Save pankaj28843/5722079 to your computer and use it in GitHub Desktop.
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 |
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.
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']
Also the regex doesn't seem to work for a password like '1234abcd#'
@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.
I seem to be having issues with the latest tastypie release.
With:
Getting:
Cannot resolve keyword 'raw_password' into field. Choices are: announcement,