Django allauth Serialization error custom User model with TimeZoneField - django

My custom User model have a TimeZoneField:
from timezone_field import TimeZoneField
class User(AbstractBaseUser, PermissionsMixin):
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
email = models.EmailField(_('email address'), unique=True, blank=False, null=False)
username = models.CharField(_('user name'), max_length=128, unique=True, blank=False, null=False)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'))
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
language = models.CharField(_('Language'), choices=settings.LANGUAGES, default=settings.ENGLISH, max_length=2)
timezone = TimeZoneField(verbose_name=_('Timezone'), default='Europe/London')
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
I use django-allauth for registration by Google accounts. When existing user (registered by google email before, not Google Account) trying login by Google Account we have error:
<DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD> is not JSON serializable
Traceback:
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
149. response = self.process_exception_by_middleware(e, request)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/handlers/base.py" in get_response
147. response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/providers/oauth2/views.py" in view
55. return self.dispatch(request, *args, **kwargs)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/providers/oauth2/views.py" in dispatch
125. return complete_social_login(request, login)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in complete_social_login
142. return _complete_social_login(request, sociallogin)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in _complete_social_login
158. ret = _process_signup(request, sociallogin)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/helpers.py" in _process_signup
25. request.session['socialaccount_sociallogin'] = sociallogin.serialize()
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/socialaccount/models.py" in serialize
189. user=serialize_instance(self.user),
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/allauth/utils.py" in serialize_instance
194. return json.loads(json.dumps(data, cls=DjangoJSONEncoder))
File "/usr/lib/python3.4/json/__init__.py" in dumps
237. **kw).encode(obj)
File "/usr/lib/python3.4/json/encoder.py" in encode
192. chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python3.4/json/encoder.py" in iterencode
250. return _iterencode(o, 0)
File "/webapps/myproject/tmp/venv/lib/python3.4/site-packages/django/core/serializers/json.py" in default
115. return super(DjangoJSONEncoder, self).default(o)
File "/usr/lib/python3.4/json/encoder.py" in default
173. raise TypeError(repr(o) + " is not JSON serializable")
Exception Type: TypeError at /accounts/google/login/callback/
Exception Value: <DstTzInfo 'Europe/London' LMT-1 day, 23:59:00 STD> is not JSON serializable
What are some ways to serialize a custom field in allauth?

My solution is replace default DefaultSocialAccountAdapter and extension serialize_instance (from allauth.utils) for serializing TimeZoneField. Don't forget set custom adapret in project settings:
SOCIALACCOUNT_ADAPTER = 'myapp.adapter.MySocialAccountAdapter'
Also I replaced pre_social_login for association Social account with Direct account (registered by email) (Thanks elssar for his example: https://stackoverflow.com/a/19443127/4012716)
myapp.adapter.py:
import json
import base64
import logging
from django.db.models import FieldDoesNotExist, FileField
from django.db.models.fields import (BinaryField)
from django.utils import six
from django.core.serializers.json import DjangoJSONEncoder
from django.shortcuts import HttpResponse
try:
from django.utils.encoding import force_text
except ImportError:
from django.utils.encoding import force_unicode as force_text
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.account.adapter import DefaultAccountAdapter
from allauth.utils import SERIALIZED_DB_FIELD_PREFIX
from allauth.exceptions import ImmediateHttpResponse
from timezone_field import TimeZoneField
from accounts.models import User
logger = logging.getLogger("django")
def my_serialize_instance(instance):
"""Instance serializer supported of serialization of TimeZoneField.
:param instance:
:return:
"""
data = {}
for k, v in instance.__dict__.items():
if k.startswith('_') or callable(v):
continue
try:
field = instance._meta.get_field(k)
if isinstance(field, BinaryField):
v = force_text(base64.b64encode(v))
elif isinstance(field, FileField):
if not isinstance(v, six.string_types):
v = v.name
elif isinstance(field, TimeZoneField):
v = six.text_type(v.zone)
# Check if the field is serializable. If not, we'll fall back
# to serializing the DB values which should cover most use cases.
try:
json.dumps(v, cls=DjangoJSONEncoder)
except TypeError:
v = field.get_prep_value(v)
k = SERIALIZED_DB_FIELD_PREFIX + k
except FieldDoesNotExist:
pass
data[k] = v
return json.loads(json.dumps(data, cls=DjangoJSONEncoder))
class MySocialAccountAdapter(DefaultSocialAccountAdapter):
"""Custom SocialAccountAdapter for django-allauth.
Replaced standard behavior for serialization of TimeZoneField.
Need set it in project settings:
SOCIALACCOUNT_ADAPTER = 'myapp.adapter.MySocialAccountAdapter'
"""
def __init__(self, request=None):
super(MySocialAccountAdapter, self).__init__(request=request)
def pre_social_login(self, request, sociallogin):
# This isn't tested, but should work
try:
emails = [email.email for email in sociallogin.email_addresses]
user = User.objects.get(email__in=emails)
sociallogin.connect(request, user)
raise ImmediateHttpResponse(response=HttpResponse())
except User.DoesNotExist:
pass
except Exception as ex:
logger.error(ex)
def serialize_instance(self, instance):
return my_serialize_instance(instance)

Related

a music app ,where i was trying to display the album detail once it is newlycreated using reverse...recieving the below mentioned error please assist

Traceback (most recent call last):
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
response = get_response(request)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\core\handlers\base.py", line 197, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\base.py", line 103, in view
return self.dispatch(request, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\base.py", line 142, in dispatch
return handler(request, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 184, in post
return super().post(request, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 153, in post
return self.form_valid(form)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 136, in form_valid
return super().form_valid(form)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 65, in form_valid
return HttpResponseRedirect(self.get_success_url())
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\views\generic\edit.py", line 125, in get_success_url
url = self.object.get_absolute_url()
File "C:\Users\Sanath\Desktop\website\music\models.py", line 12, in get_absolute_url
return reverse('music:detail',kwargs = {'pk' : self.pk})
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\urls\base.py", line 88, in reverse
return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
File "C:\Users\Sanath\anaconda3\lib\site-packages\django\urls\resolvers.py", line 828, in _reverse_with_prefix
raise NoReverseMatch(msg)
django.urls.exceptions.NoReverseMatch: Reverse for 'detail' with keyword arguments '{'pk': 11}' not found. 1 pattern(s) tried: ['music/(?P<album_id>[0-9]+)/\\Z']
urls.py
from django.urls import path
from . import views
app_name = 'music'
urlpatterns = [
#music/
path('', views.index,name = 'index'),
#music/71/
path('<int:album_id>/', views.detail,name = 'detail'),
path("register/",views.UserFormView.as_view(),name = 'register'),
path("album/add/",views.albumcreate.as_view(),name = 'album-add'),
#url pattern for the view albumcreate
]
models.py
from django.db import models
from django.urls import reverse
# Create your models here.
class album(models.Model): #inherit from models.Model
artist = models.CharField(max_length=250) #when migrated to db it will be a column with the same name automatically(here it is variables
#charfield ,foreignkey is datatype
album_title = models.CharField(max_length=500)
genre = models.CharField(max_length=100)
album_logo = models.CharField(max_length=1000)
def get_absolute_url(self):
return reverse('music:detail',kwargs = {'pk' : self.pk})
#returns the detail page of the album we just created
def __str__(self):
return self.album_title+'-'+self.artist#string representation of obj
class songs(models.Model):
#songs needs to be part of an album
album = models.ForeignKey(album,on_delete=models.CASCADE)
#bts another column with unique id 1st album id 1 2nd album id 2 and so on
#let pk of red = 1 then a song of that album will have fk as 1 so that they are linked
#when ever we delete the ablum red all the songs in the album should be deleted hence delete
file_type= models.CharField(max_length=10) #mp3etc
song_title = models.CharField(max_length=250)
def __str__(self):
return self.song_title
views.py
from django.http import HttpResponse
from .models import album
from django.shortcuts import render,redirect
from django.contrib.auth import login,logout,authenticate
from django.http import Http404
from django.views.generic import View
from .forms import UserForm
from django.views.generic.edit import CreateView,UpdateView,DeleteView # form to create new ,deleting,updating obj
def index(request):
all_albums = album.objects.all()
'''
html =''
for album1 in all_albums:
path='/music/' + str(album1.id) + '/'
html += '' + album1.album_title + '<br>'
'''
# template = loader.get_template('music/index.html')
context = { 'all_albums': all_albums,
}
#return HttpResponse(template.render(context,request))
#same thing as i did in alb1 code excpet i have done it though html file to prevent making a mess here
return render(request,'music/index.html',context)
#same as above but using render shortcut which internally creates http response
def detail(request, album_id):
try:
alb = album.objects.get(pk=album_id)
except album.DoesNotExist:
raise Http404("album does not exist")
return render(request,'music/detail.html',{ 'alb': alb})
#not using context because we make use of only one album at one time and there ia no iterating over
class UserFormView(View):
form_class = UserForm
template_name = 'music/reg_form.html'
def get(self,request):
# when ever user requests from this form this fn is called
#display a blank form
form = self.form_class(None) # nothing is present we have to input it
return render(request,self.template_name,{form:form})
#self. teemp name = where u want to return ,what html#
def post(self,request):
# when ever user submits the above form this fn is called
form = self.form_class(request.POST) # what ever is entered in get is posted
if form.is_valid():
user= form.save(commit=False) #storing itlocally not yet commited to db
#cleaned normalized data(so that everyone uses the gemeral format and can be entered into the db properly
username = form.cleaned_data['username']
password = form.cleaned_data['password']
user.set_password(password) #method to set password we cannot do like user.password = 'raj' or = password
user.save()
# return user obj id credentials are correct
user = authenticate(username=username,password = password)
#what the above fun does it that it takes the username and pwd and chcecks with the db is they are an actual user
if user is not None: #for confirming above fn
if user.is_active:
login(request,user)
#logged in to the website
#request.user.username
return redirect('music:index')
return render(request,self.template_name,{form:form})
#new classsthat creates the form view for get abs url fn
class albumcreate( CreateView ): #using models and not by admin or shell queries but in the webpage itself
model = album
fields = ['artist','album_title','genre','album_logo']
#we havent specified template name cuz _form is the default file it is going to look at
expected result was to open up and show the details of the album i created
.............................................................................................................................................................................................................................................................................................................................................................................................................................................
The problem is in here:
def get_absolute_url(self):
return reverse('music:detail',kwargs = {'pk' : self.pk})
^^^^^^^^^^^^^^^^
In the urls, you are passing album_id, whereas here you are passing pk. Hence changing it to àlbum_id should fix it:
def get_absolute_url(self):
return reverse('music:detail',kwargs = {'album_id' : self.pk})

Need help in django model

I have written a model for my django project.
This is my model
from django.utils.translation import ugettext_lazy as _
from django.db import models
from django.utils.crypto import get_random_string
from django.db import models
from django.contrib.auth.models import(
BaseUserManager,
AbstractBaseUser,
PermissionsMixin,
)
def generate_vid():
"""Generates a vid for the users"""
not_unique = True
while not_unique:
vid = get_random_string(10, 'abcdefg0123456789')
if not User.objects.filter(v_id = vid).exists():
not_unique=False
return vid
class UserManager(BaseUserManager):
"""Model for user manager"""
def create_user(self, username, password, **params):
"""Create and return a user"""
u_type = params.pop('usertype','v')
params.update({'usertype':u_type})
p_username = params.pop('parent_username', 0)
if(u_type=='v'):
pass
else:
parent_id = User.objects.filter(username = p_username).values_list('v_id')
params.update({'parent_id': parent_id})
user = self.model(username=username, **params)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, password, **params):
"""Create and return a user"""
params.setdefault('is_staff',True)
params.setdefault('is_superuser',True)
params.setdefault('is_active',True)
if params.get('is_staff') is not True:
raise ValueError(_('Superuser must have is_staff=True.'))
if params.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser=True.'))
return self.create_user(username, password, **params)
class User(AbstractBaseUser, PermissionsMixin):
"""Models for user"""
v_id = models.CharField(
max_length=10,
default=generate_vid,
primary_key = True,
)
username = models.CharField(max_length=20, unique=True)
email = models.EmailField(blank=True, unique = True)
parent_id = models.ForeignKey('User', on_delete=models.SET_DEFAULT, default=0)
usertype = models.CharField(max_length=1, choices=[('f', 'family'), ('v', 'veteran')])
REQUIRED_FIELDS = ['usertype']
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
USERNAME_FIELD = 'username'
objects = UserManager()
def __str__(self):
return self.username
Now I want to impose the condition while creating a user such that every time I provide usertype=f, and I provide a username(say username='Test")
the parent_id of that particular entry is automatically set as the v_id of the username provided.
The parent_id is a self referential foreignkey.
This is the error showing while testing the feature
File "/py/lib/python3.9/site-packages/rest_framework/serializers.py", line 205, in save
self.instance = self.create(validated_data)
File "/app/user/serializers.py", line 17, in create
return get_user_model().objects.create_user(**validated_data)
File "/app/base/models.py", line 39, in create_user
user = self.model(username=username, **params)
File "/py/lib/python3.9/site-packages/django/db/models/base.py", line 485, in __init__
_setattr(self, field.name, rel_obj)
File "/py/lib/python3.9/site-packages/django/db/models/fields/related_descriptors.py", line 215, in __set__
raise ValueError(
ValueError: Cannot assign "<QuerySet []>": "User.parent_id" must be a "User" instance.
I think you can try signals to set data after creating a user account. You can choose from several types, but I would recommend you focus on pre_save and post_save.
UPDATE
I wrote examples, but the website will probably better illustrate it. In general, there are quite a few signals, but the most commonly used are pre_save and post_save.
#2 UPDATE
Try use a first or latest. Details in documentation.
parent_id = User.objects.filter(username = p_username).first()

Creating Custom User Class in Django

I've read and re-read the other questions regarding this issue and I'm still unable to create a custom Django User model. I keep getting the error: Manager isn't available; 'auth.User' has been swapped for 'Users.User'.
If not clear by the error, I've created a Users app in which the models.py file defines a custom user class User.
Here's are the relevant files from my project, UserTest:
Registering the app, specifying the custom user model:
UserTest> UserTest> settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'Users'
]
AUTH_USER_MODEL = 'Users.User'
Extending the default Django User class via my User model and connecting it to a Profile model (not strictly relevant to the issue but a fundamental aspect of the approach.)
UserTest > Users > models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
email = models.CharField(max_length=128, blank=False, unique=True, verbose_name='Email', name='email')
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
...
Registering the new User model in my Users App's admin.py (not sure if this is required for testing quick front-end functionality?)
UserTest > Users > admin.py
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import User
admin.site.register(User, UserAdmin)
I've created some basic forms and a view to allow front-end submission of relevant user and profile data. Given the error, I didn't feel they were relevant but I can include them if someone feels they are. Again, the error is as follows:
Manager isn't available; 'auth.User' has been swapped for 'Users.User'
I had initially created a custom User model using the AbstractBaseUser class, which the documentation describes states a custom Manager class must also be created. I had the same error using that approach and didn't read that the same customization was needed when using this approach, relying on AbstractUser
In my UserTest > Users > forms.py file, I've tried accessing my Users.models.User model in both of the following ways:
By directly importing it as such:
from .models import User
By using Django's get_user_model() function as such:
from django.contrib.auth import get_user_model
User = get_user_model()
The second approach seems to have solved this issue for many others, such as the question HERE but doesn't help me (unless I'm missing something).
The full traceback of the error is here:
Internal Server Error: /test-form/
Traceback (most recent call last):
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\core\handlers\exception.py", line 34, in inner
response = get_response(request)
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\core\handlers\base.py", line 126, in _get_response
response = self.process_exception_by_middleware(e, request)
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\core\handlers\base.py", line 124, in _get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "C:\Users\...\Desktop\UserTest\Users\views.py", line 19, in NewUserRegistration
print("USERFORM:", user_form, type(user_form), dir(user_form))
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\utils\html.py", line 397, in <lambda>
klass.__str__ = lambda self: mark_safe(klass_str(self))
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\forms\forms.py", line 142, in __str__
return self.as_table()
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\forms\forms.py", line 284, in as_table
errors_on_separate_row=False,
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\forms\forms.py", line 202, in _html_output
top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\forms\forms.py", line 313, in non_field_errors
return self.errors.get(NON_FIELD_ERRORS, self.error_class(error_class='nonfield'))
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\forms\forms.py", line 180, in errors
self.full_clean()
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\forms\forms.py", line 383, in full_clean
self._post_clean()
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\contrib\auth\forms.py", line 107, in _post_clean
super()._post_clean()
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\forms\models.py", line 403, in _post_clean
self.instance.full_clean(exclude=exclude, validate_unique=False)
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\db\models\base.py", line 1137, in full_clean
self.clean()
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\contrib\auth\models.py", line 338, in clean
self.email = self.__class__.objects.normalize_email(self.email)
File "C:\Users\...\Desktop\UserTest\venv\lib\site-packages\django\db\models\manager.py", line 188, in __get__
cls._meta.swapped,
AttributeError: Manager isn't available; 'auth.User' has been swapped for 'Users.User'
[26/Oct/2018 09:47:02] "POST /test-form/ HTTP/1.1" 500 118088
I appreciate any help. Usually, if I'm able to type an entire post on SO without having an "aha" moment I know I'm pretty screwed.
UPDATE 1: Adding forms.py and views.py
In my UserTest > Users app, the views.py and forms.py are as follows:
UserTest > Users > forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.core.exceptions import ValidationError
from django.contrib.auth import get_user_model
User = get_user_model()
class NewUser(UserCreationForm):
email = forms.EmailField(max_length=96)
password = forms.PasswordInput()
username = forms.CharField(max_length=18)
def clean_email(self):
email = self.cleaned_data['email'].lower()
r = User.objects.filter(email=email)
if r.count():
raise ValidationError("Email already exists")
return email
def save(self, commit=True):
user = User.objects.create_user(
self.cleaned_data['email'],
self.cleaned_data['password'],
self.cleaned_data['username']
)
return user
NOTE: Here I have tried both the get_user_model() approach as well as importing my custom User class from Users.models and neither resolve the error.
UserTest > Users > views.py
from django.shortcuts import render
from django.contrib import messages
from .forms import NewUser
def NewUserRegistration(request):
if request.method == 'POST':
user_form = NewUser(request.POST)
if user_form.is_valid():
user_form.save()
messages.success(request, 'Account Created Successfully!')
else:
user_form = NewUser()
return render(request, 'Users/test-form.html', {'user_form': user_form})
As the documentation explains, forms such as UserCreationForm" are tied to User and need to be rewritten or extended to work with a custom user model".
So, as shown there, you need to override the inner Meta class to point to your model:
class NewUser(UserCreationForm):
...
class Meta(UserCreationForm.Meta):
model = get_user_model()
When you use custom user model it needs to define its objects i.e its manager which contains its various methods like create_user or create_superuser.
You can use BaseUserManager to inherit in your custom manager and define it in your User model.
It could be like -
class UserManager(BaseUserManager):
def create_user(self, email, date_of_birth, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, date_of_birth, password):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
email,
password=password,
date_of_birth=date_of_birth,
)
user.is_admin = True
user.save(using=self._db)
return user
class User(AbstractUser):
email = models.CharField(max_length=128, blank=False, unique=True, verbose_name='Email', name='email')
objects = UserManager()
This is quit simple example, you can use UserManager as per your requirements.
It is recommenced to refer documentation for more clear understanding - https://docs.djangoproject.com/en/2.1/topics/auth/customizing/

User creation with unique constraint in Django Rest framework

I researched about it in Google, and have tried in lot of ways but still not able to get it right. Here are the requirements:
A one to one field for extending User model, so this has been achieved and the new model is called Customer.
Now, with new user 201 response is returned BUT not all the data is serialized, only date_of_birth is coming in json format, I want even the User to be in that json response.
If I try to add a user with a username which already exists it plays up really bad. I am trying with Try and Except but it really doesnt work. I want a response of 409 conflict to be sent if the username already exists.
Here is UserSerializer.py:
-
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
new_username = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('url', 'pk', 'username', 'email', 'is_staff', 'new_username')
extra_kwargs = {
'username': {'validators': []},
}
def get_new_username(self, obj):
return obj.username
Here is Customer model:
from django.db import models
from django.contrib.auth.models import User
class Customer(models.Model):
user = models.OneToOneField(User, related_name="customer", on_delete=models.CASCADE)
date_of_birth = models.DateField(max_length=8)
def __unicode__(self):
return u'%s' % self.user.username
Here is CustomerSerializer class:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from rest_framework import serializers, status
from rest_framework.response import Response
from customers.models import Customer
from api.serializers import UserSerializer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Customer
fields = ('url', 'date_of_birth', 'user')
def create(self, validated_data):
print "coming inside serializer create"
user_data = validated_data.pop("user")
print user_data
try:
userinstance = User.objects.get_or_create(**user_data)[0]
print "user..."
print userinstance
print validated_data
customer = Customer.objects.create(user=userinstance, **validated_data)
print customer.user
return customer
except Exception as exception:
print exception
# print "customer --> %s " % customer
return customer
def update(self, instance, validated_data):
print "coming inside update"
user_data = validated_data.pop("user")
username = user_data.pop('username')
user = get_user_model().objects.get_or_create(username=username)[0]
user.username = username
user.email = user_data.get('email', user.email)
user.save()
# instance.user = user
instance.date_of_birth = validated_data.get('date_of_birth', instance.date_of_birth)
instance.save()
And here is view set for Customer:
from rest_framework import viewsets
from customers.models import Customer
from customers.serializers import CustomerSerializer
from api.permissions import IsOwnerOrAdmin
from rest_framework import authentication, permissions, status
from rest_framework.response import Response
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
authentication_classes = (authentication.TokenAuthentication,
authentication.SessionAuthentication,
authentication.SessionAuthentication, )
def get_permissions(self):
if self.action == 'list':
self.permission_classes = (permissions.IsAdminUser,)
elif self.action == 'create':
self.permission_classes = (permissions.AllowAny,)
return super(self.__class__, self).get_permissions()
def create(self, request, *args, **kwargs):
print "This is view create -----------------------------"
serializer = self.get_serializer(data=request.data)
# print serializer
if serializer.is_valid(): # It passes because here there are no new objects created yet
print "serializer is valid ......"
# self.pre_save(serializer.object)
# user_data = serializer.validated_data.get("user")
# print user_data
self.object = serializer.create(serializer.validated_data) # It creates the User (triggering the signal) instance and then when saving UserProfile, it give the integrity error
# self.post_save(self.object, created=True)
# headers = self.get_success_headers(serializer.data)
print 'coming here ....1'
print self.object
return Response(serializer.validated_data, status=status.HTTP_201_CREATED)
print 'coming here..'
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
So, basically I would like to create new customers with all data returned as response and status 201 and if username already exists, then 409 or status code which I define and the some data which DRF should not complaint about i.e; right now it says that OrderDict does not contain PK if I modify the serializer.
Thanks
Edit 1
Here is updated serilizer with custom exception:
from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.db import IntegrityError
from rest_framework import serializers, status
from rest_framework.response import Response
from rest_framework.exceptions import APIException
from customers.models import Customer
from api.serializers import UserSerializer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Customer
fields = ('url', 'pk', 'date_of_birth', 'user')
def create(self, validated_data):
print "coming inside serializer create"
user_data = validated_data.pop("user")
print user_data
try:
userinstance = User.objects.create_user(**user_data)
print "user..."
print userinstance
print validated_data
customer = Customer.objects.create(user=userinstance, **validated_data)
print customer.user
return customer
# except TypeError as exception:
# print exception
# # print "customer --> %s " % customer
# raise TypeError(exception)
except IntegrityError as exception:
raise Custom409(exception)
def update(self, instance, validated_data):
print "coming inside update"
user_data = validated_data.pop("user")
username = user_data.pop('username')
user = get_user_model().objects.get_or_create(username=username)[0]
user.username = username
user.email = user_data.get('email', user.email)
user.save()
# instance.user = user
instance.date_of_birth = validated_data.get('date_of_birth', instance.date_of_birth)
instance.save()
return instance
class Custom409(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = "User already there."
But still get :
Traceback (most recent call last):
File "/home/naveen/projects/gratis/customers/tests.py", line 37, in test_if_anyone_could_create_customers
format='json')
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 299, in post
path, data=data, format=format, content_type=content_type, **extra)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 221, in post
return self.generic('POST', path, data, content_type, **extra)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/test/client.py", line 379, in generic
return self.request(**r)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 288, in request
return super(APIClient, self).request(**kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/test.py", line 240, in request
request = super(APIRequestFactory, self).request(**kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/test/client.py", line 466, in request
six.reraise(*exc_info)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 132, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/django/views/decorators/csrf.py", line 58, in wrapped_view
return view_func(*args, **kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/viewsets.py", line 83, in view
return self.dispatch(request, *args, **kwargs)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/views.py", line 477, in dispatch
response = self.handle_exception(exc)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/views.py", line 437, in handle_exception
self.raise_uncaught_exception(exc)
File "/home/naveen/.virtualenvs/gratis/local/lib/python2.7/site-packages/rest_framework/views.py", line 448, in raise_uncaught_exception
raise exc
IntegrityError: duplicate key value violates unique constraint "auth_user_username_key"
DETAIL: Key (username)=(user2) already exists.
And also the test case is as follows:
def test_if_anyone_could_create_customers(self):
create_user = self.client.post('/api/customers/',
{'user':{'username': 'user2', 'email': 'user2#gmail.com'}, 'date_of_birth':"1982-10-20"},
format='json')
print create_user
self.assertEqual(create_user.status_code, 201)
create_user = self.client.post('/api/customers/',
{'user': {'username': 'user2', 'email': 'user2#gmail.com'},'date_of_birth': "1982-10-20"},
format='json')
print create_user
# no duplicates
user = User.objects.all()
print user
self.assertEqual(create_user.status_code, 409)
This is because you are catching exceptions far too broadly in your create method. In fact you should never do this (regardless of whether you use DRF or not)
except Exception as exception:
print exception
# print "customer --> %s " % customer
return customer
You should only catch the specific exceptions that you need to catch. And you shouldn't be returning a customer at all. In this case, examination of ModelSerializer tells us that the only one you really should catch is TypeError. Even that is raised again for the caller to handler.
except TypeError as exception:
print exception
# print "customer --> %s " % customer
raise TypeError(exception)
Now you should get what you want but according to your comments, you are not so try raising a custom error.
from rest_framework.exceptions import APIException
from django.utils.encoding import force_text
class Custom409(APIException):
status_code = status.HTTP_409_CONFLICT
default_detail = 'A conflict occurred'
And then
except IntergrityError as ext:
print exception
# print "customer --> %s " % customer
raise Custom409(ext)
The your code is raising IntegrityErorr because you are violated unique constraint while creating profile.
You should use get_or_create while creating user profile/customer instance. Something like this should work.
# CustomerSerializer
def create(self, validated_data):
"""...snip..."""
try:
userinstance = User.objects.get_or_create(**user_data)[0]
customer = Customer.objects.get_or_create(
user=userinstance, defaults=validated_data)[0]
return customer
except Exception as exception:
# custom validationerror
raise ConflictError({"user": "User already exists"})
Maybe you should try to catch it in your view directly:
views.py
from rest_framework import viewsets
from customers.models import Customer
from customers.serializers import CustomerSerializer
from api.permissions import IsOwnerOrAdmin
from rest_framework import authentication, permissions, status
from rest_framework.response import Response
class CustomerViewSet(viewsets.ModelViewSet):
serializer_class = CustomerSerializer
queryset = Customer.objects.all()
authentication_classes = (authentication.TokenAuthentication,
authentication.SessionAuthentication,
authentication.SessionAuthentication, )
def get_permissions(self):
if self.action == 'list':
self.permission_classes = (permissions.IsAdminUser,)
elif self.action == 'create':
self.permission_classes = (permissions.AllowAny,)
return super(self.__class__, self).get_permissions()
def create(self, request, *args, **kwargs):
print "This is view create -----------------------------"
serializer = self.get_serializer(data=request.data)
# print serializer
try:
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
except ValidationError as e:
if e.detail.get('username') == ['user with this username already exists.']:
raise Custom409()
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
And removing the logic you put in the serializer create. DRF exceptions are meant to be used in views not serializer.

Flask Roles AttributeError: 'User' object has no attribute 'has_roles'

I am getting the error below. I think that for some reason my UserMixin import does not include the has_role property that #role_required requires. Do I need to use RoleMixin. Can anyone help?
Traceback (most recent call last):
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask_debugtoolbar/__init__.py", line 125, in dispatch_request
return view_func(**req.view_args)
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask_login.py", line 792, in decorated_view
return func(*args, **kwargs)
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/flask_user/decorators.py", line 69, in decorated_view
if not current_user.has_roles(*role_names):
File "/Users/henry.arnold/anaconda/lib/python3.5/site-packages/werkzeug/local.py", line 338, in __getattr__
return getattr(self._get_current_object(), name)
AttributeError: 'User' object has no attribute 'has_roles'
My model
import datetime as dt
from flask_login import UserMixin
from flaskapp.database import Column, Model, SurrogatePK, db
from flaskapp.teams.models import Teams
class User(UserMixin, SurrogatePK, Model):
"""A user of the app."""
__tablename__ = 'users'
user_id = Column(db.BigInteger, unique=True, nullable=False)
first_name = Column(db.String(30), nullable=True)
last_name = Column(db.String(80), nullable=True)
email = Column(db.String(80), unique=True, nullable=False)
isaf_id = Column(db.String(10), nullable=True)
primary_team_id = Column(db.Integer, db.ForeignKey(Teams.id), nullable=True)
primary_team = db.relationship('Teams', foreign_keys='User.primary_team_id')
is_admin = Column(db.Boolean(), default=False)
created_at = Column(db.DateTime, nullable=False, default=dt.datetime.utcnow)
roles = db.relationship('Role', secondary='user_roles',
backref=db.backref('users', lazy='dynamic'))
def __init__(self, user_id=user_id, first_name=first_name, last_name=last_name, email=email, is_admin=is_admin, **kwargs):
"""if int(user_id) in current_app.config['ADMINS']:
self.is_admin = True
else:
self.is_admin = False"""
db.Model.__init__(self, user_id=user_id, first_name=first_name, last_name=last_name, email=email, is_admin=is_admin, **kwargs)
"""Create instance."""
#property
def full_name(self):
"""Full user name."""
return '{0} {1}'.format(self.first_name, self.last_name)
def __repr__(self):
"""Represent instance as a unique string."""
return '{0} {1}'.format(self.first_name, self.last_name)
# Define Role model
class Role(db.Model):
role_id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), unique=True)
# Define UserRoles model
class UserRoles(db.Model):
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('users.user_id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.role_id', ondelete='CASCADE'))
to create the user I do the following
#blueprint.route('/callback/<provider>')
def oauth_callback(provider):
if not current_user.is_anonymous:
return redirect(url_for('public.home'))
oauth = OAuthSignIn.get_provider(provider)
user_id, first_name, last_name, email, is_admin = oauth.callback()
if user_id is None:
flash('Authentication failed.')
return redirect(url_for('public.home'))
user = User.query.filter_by(user_id=user_id).first()
if not user:
User.create(user_id=user_id, first_name=first_name, last_name=last_name, email=email, is_admin = is_admin)
user = User.query.filter_by(user_id=user_id).first()
if is_admin:
print('is admin')
admin = Role.query.filter(Role.name == 'admin').first()
user.roles.append(admin)
User.query.filter_by(user_id=user_id).update({'is_admin': True})
else:
print('is not admin')
User.query.filter_by(user_id=user_id).update({'is_admin': False})
login_user(user, True)
db.session.commit()
return redirect(url_for('public.home'))
I think check the user is an admin
from flask import Blueprint, flash, redirect, render_template, request, url_for
from flask_login import current_user, login_required
from flask_user import roles_required
from flaskapp.events.forms import CreateEventForm
from flaskapp.events.models import Events
from flaskapp.database import db
from datetime import date
from collections import OrderedDict
#blueprint.route('/add_event', methods=['POST', 'GET'])
#login_required
#roles_required('admin')
def add_event():
UserMixin class from flask_login does not provide has_roles attribute (source). You can provide it by yourself in your User class:
class User(UserMixin, SurrogatePK, Model):
# ... Everything that you've written so far
def has_roles(self, *args):
return set(args).issubset({role.name for role in self.roles})
Also, you can use UserMixin class not from flask_login module, but from flask_user module (yes, there is a confusion in names). The latter class indeed does provide has_roles method. In this case all you need to do is change this line
from flask_login import UserMixin
to this:
from flask_user import UserMixin