-
-
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,