django-allauth: custom user generates IntegrityError at /accounts/signup/ (custom fields are nulled or lost) - django-allauth

I'm trying to integrate django-allauth with a custom user model (subclassed AbstractUser, but when I test the signup form I get an integrity error due to field (date_of_birth) being null, but the value submitted was u'1976-4-6'
I'm learning the new custom user stuff, as well as class-based views as I'm learning django-allauth, so I'm confident that I'm doing something wrong, but after a couple days of reading the github issues, the few tutorials, readthedocs, and stackoverflow questions I still have no clear idea of what I'm doing wrong (well I know one thing I'm doing wrong: trying different solutions here and there, so I definitely have a miss-mosh of implementations)
But, I can't find a good answer on how to integrate allauth with a subclassed AbstractUser, so if anyone could enlighten me, I would really appreciate it.
(Note - the site is more or less working when I log in as a user that I've loaded via fixtures, so please assume that non-django-allauth omissions are omissions - if you need clarification on something not below, I will happily edit)
settings.py
AUTH_USER_MODEL = 'userdata.CtrackUser'
ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
ACCOUNT_SIGNUP_FORM_CLASS = 'userdata.forms.SignupForm'
LOGIN_REDIRECT_URL = '/profile'
SOCIALACCOUNT_QUERY_EMAIL = True
SOCIALACCOUNT_AUTO_SIGNUP = False
ACCOUNT_USER_MODEL_USERNAME_FIELD = 'username'
userdata/models.py
class CtrackUser(AbstractUser):
date_of_birth = models.DateField(help_text='YYYY-MM-DD format')
gender = models.CharField(max_length=2,
choices=settings.GENDER_CHOICES, blank=True)
race = models.CharField(max_length=2, choices=settings.RACE_CHOICES, null=True, blank=True)
condition = models.ForeignKey(Condition, null=True, blank=True)
location = models.CharField(max_length=255, null=True, blank=True)
my_symptoms = models.ManyToManyField(Symptom)
is_admin = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
userdata/forms.py
from django import forms
from django.conf import settings
from django.contrib.auth import get_user_model
from allauth.account.forms import SetPasswordField, PasswordField
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from medical.models import Condition
class SignupForm(forms.Form):
email = forms.EmailField(required=True,)
username = forms.CharField(max_length=80,required=True,)
password1 = SetPasswordField()
password2 = PasswordField()
first_name = forms.CharField(max_length=100,required=False,)
last_name = forms.CharField(max_length=100, required=False,)
date_of_birth = forms.DateField()
gender = forms.TypedChoiceField(
choices=settings.GENDER_CHOICES,
widget=forms.Select(attrs={'class': 'input-lg'}),
required=False,)
race = forms.TypedChoiceField(
choices=settings.RACE_CHOICES,
widget=forms.Select(attrs={'class': 'input-lg'}),
required=False,)
location = forms.CharField(max_length=255,required=False,)
condition = forms.ModelChoiceField(
queryset=Condition.objects.all(),
widget=forms.Select(attrs={'class': 'input-lg'}),
empty_label='Select condition (optional)'
)
class Meta:
model = get_user_model() # use this function for swapping user model
fields = ('email', 'username', 'password1', 'password2', 'first_name', 'last_name',
'date_of_birth', 'gender', 'race', 'location', 'condition', 'confirmation_key',)
def __init__(self, *args, **kwargs):
super(SignupForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'signup_form'
self.helper.label_class = 'col-xs-6'
self.helper.field_class = 'col-xs-12'
self.helper.form_method = 'post'
self.helper.form_action = 'accounts_signup'
self.helper.add_input(Submit('submit', 'Sign up'))
def signup(self, request, user, model):
user.username = self.cleaned_data['username']
user.first_name = self.cleaned_data['first_name']
user.last_name = self.cleaned_data['last_name']
model.date_of_birth = self.cleaned_data['date_of_birth']
model.gender = self.cleaned_data['gender']
model.race = self.cleaned_data['race']
model.location = self.cleaned_data['location']
model.condition = self.cleaned_data['condition']
model.save()
user.save()
templates/allauth/account/signup.html
<form id="signup_form" method="post" action="{% url 'account_signup' %}" class="form-inline">
{% csrf_token %}
{% crispy form %}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %}
{# <div class="form-actions">#}
{# <button class="btn btn-primary" type="submit">Sign Up</button>#}
{# </div>#}
</form>
POST data
u'condition' [u'1']
u'confirmation_key' [u'']
u'date_of_birth' [u'1976-4-6']
u'email' [u'1#bt.co']
u'first_name' [u'One']
u'gender' [u'']
u'last_name' [u'Person']
u'location' [u''] u'password1' [u'123456']
u'password2' [u'123456']
u'race' [u'']
u'submit' [u'Sign up']
u'username' [u'gn']
Error generated (note difference from post data)
Exception Type: IntegrityError at /accounts/signup/
Exception Value: null value in column "date_of_birth" violates not-null constraint
DETAIL: Failing row contains (19, pbkdf2_sha256$12000$exNVzh4QI0Rb$mCTz9Tc+TIBbD8+lIZs2B3hqjxd+qmI..., 2014-07-02 16:27:43.751428+00, f, gn, One, Person, 1#bt.co, f, t, 2014-07-02 16:27:43.751473+00, null, , null, null, null, f, 2014-07-02 16:27:43.833267+00, 2014-07-02 16:27:43.83329+00).
Full traceback here:
https://gist.githubusercontent.com/hanleybrand/ee260b53dfb404f5055a/raw/3325dc746120c4f7521b9b976abce45dd7d71a77/gistfile1.txt

The answer -- which I'm still figuring out -- seems to be that if you are saving a model that contains field types that allauth.account.adapter.DefaultAccountAdapter doesn't handle correctly (e.g. any field that lacks a __getitem__ attribute, like models.DateField) it is necessary to implement a custom adapter somewhat like below.
note: your subclassed abstract user model is the user that's passed in, so the best practice is to use the form data directly like user.email = data.get('email') rather than using the allauth internal methods used in the DefaultAccountAdapter class
userdata/adapter.py
class AccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
data = form.cleaned_data
user.email = data.get('email')
user.username = data.get('username')
# all your custom fields
user.date_of_birth = data.get('date_of_birth')
user.gender = data.get('gender')
if 'password1' in data:
user.set_password(data["password1"])
else:
user.set_unusable_password()
self.populate_username(request, user)
if commit:
user.save()
return user

Related

ODOO 12 : show field of model “purchase.order” in form view of “account.invoice”

I added commission field in purchase order model and i want to show it in account invoice when i click on "create bill". below is my code but it doesn't work, I hope somebody can help me. Many thanks in advance.
class ConfirmComm(models.Model):
_inherit = "purchase.order"
commission = fields.Float(string='Commission', required='true', default=0)
#api.multi
def action_view_invoice(self, cr, uid, order, context=None):
if context is None:
context = {}
journal_id = self.pool['account.invoice'].default_get(cr, uid, ['journal_id'], context=context)['journal_id']
if not journal_id:
raise UserError(_('Error!'),
_('Please define purchase journal for this company: "%s" (id:%d).') % (
order.company_id.name, order.company_id.id))
invoice_vals = {
'name': order.partner_ref or '',
'origin': order.name,
# 'type': 'in_invoice',
# Sale order id as source_id
# 'source_id': order.id,
'reference': order.partner_ref or order.name,
'account_id': order.partner_invoice_id.property_account_receivable.id,
'partner_id': order.partner_invoice_id.id,
'journal_id': journal_id,
'commission': order.commission,
# 'invoice_line': [(6, 0, lines)],
'currency_id': order.pricelist_id.currency_id.id,
# 'comment': order.note,
'payment_term_id': order.payment_term_id or False,
'fiscal_position_id': order.fiscal_position_id,
'date_invoice': context.get('date_invoice', False),
'company_id': order.company_id.id,
'user_id': order.user_id and order.user_id.id or False,
}
_logger.info("KTR this is commissionTest $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ %d", order.commission)
invoice_vals.update(self._inv_get(cr, uid, order, context=context))
return invoice_vals
You need to add this field to model account.invoice, too, because there is no direct relation between purchase.order and account.invoice to use a related field or something fancy. To see that field in the invoice form view, you have to add it there, too (as usual).
The rest of your code should be okay, because setting the value by using in it purchase.order.action_view_invoice() would be the next important part, but you've already done that ;-)

Spring Security UI Grails only search/create/edit users for the admin?

I am currently working on a solution in Grails and I have installed the following security plugins:
Spring Security Core
Spring Security UI
I will basically have a solution with the following security structure:
Super Users
Admins(For different business areas)
Users (within the different business areas)
So basically I installed the Spring Security UI in order to allow the various Business Area Admins manage their own areas, they should be able to use the UI in order to allow them to search only for users in thier own area, create users in their own area and edit users only in their own area. However the spring security UI gives people who have access blanket access do anything.
I have added an extra field to the spring security domain model which is "Area", so I was thinking when the admin is searching for users they would only see users in the same area as them, when they create a user they can only do so for their own area and they can only edit users in their own area.
Below is some code that the spring security UI uses to search for the users, can I modify this in order to only return the users that are in the same area as the admin who is currently logged in? or is there a better way?
def userSearch = {
boolean useOffset = params.containsKey('offset')
setIfMissing 'max', 10, 100
setIfMissing 'offset', 0
def hql = new StringBuilder('FROM ').append(lookupUserClassName()).append(' u WHERE 1=1 ')
def queryParams = [:]
def userLookup = SpringSecurityUtils.securityConfig.userLookup
String usernameFieldName = userLookup.usernamePropertyName
for (name in [username: usernameFieldName]) {
if (params[name.key]) {
hql.append " AND LOWER(u.${name.value}) LIKE :${name.key}"
queryParams[name.key] = params[name.key].toLowerCase() + '%'
}
}
String enabledPropertyName = userLookup.enabledPropertyName
String accountExpiredPropertyName = userLookup.accountExpiredPropertyName
String accountLockedPropertyName = userLookup.accountLockedPropertyName
String passwordExpiredPropertyName = userLookup.passwordExpiredPropertyName
for (name in [enabled: enabledPropertyName,
accountExpired: accountExpiredPropertyName,
accountLocked: accountLockedPropertyName,
passwordExpired: passwordExpiredPropertyName]) {
Integer value = params.int(name.key)
if (value) {
hql.append " AND u.${name.value}=:${name.key}"
queryParams[name.key] = value == 1
}
}
int totalCount = lookupUserClass().executeQuery("SELECT COUNT(DISTINCT u) $hql", queryParams)[0]
Integer max = params.int('max')
Integer offset = params.int('offset')
String orderBy = ''
if (params.sort) {
orderBy = " ORDER BY u.$params.sort ${params.order ?: 'ASC'}"
}
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u $hql $orderBy",
queryParams, [max: max, offset: offset])
def model = [results: results, totalCount: totalCount, searched: true]
// add query params to model for paging
for (name in ['username', 'enabled', 'accountExpired', 'accountLocked',
'passwordExpired', 'sort', 'order']) {
model[name] = params[name]
}
render view: 'search', model: model
}
EDIT....
I believe it may have something to do with the code below:
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u $hql $orderBy",
queryParams, [max: max, offset: offset])
I think I just need to alter this statement so that it looks for the list of users where the currently logged in users "Area" is equal to the same area as the users. Can anyone please help me with this??
EDIT 2.....
I have now looked into this and have been able to obtain the users Area and now alls I need to do is to modify the query to the database to look for the users that have the same Area as the admin searching. I have tried the following with no luck, can someone please help me with this as I know this must be simple just cant seem to get there :-S
def user = springSecurityService.currentUser
def userArea = user.area
def hql = new StringBuilder('FROM ').append(lookupUserClassName()).append(' u WHERE 1=1 AND u.area = userArea')
EDIT 3.......
Thanks so much half of my problem is solved lol, now just the Ajax piece:
I have tried the below code in order to modify the search for the Ajax function to only return results where the Area of the user is the same as the currently logged in user:
String username = params.term
String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
def user = springSecurityService.currentUser
setIfMissing 'max', 10, 100
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u.$usernameFieldName " +
"FROM ${lookupUserClassName()} u " +
"WHERE LOWER(u.$usernameFieldName) LIKE :name AND LOWER(u.area) = :area " +
"ORDER BY u.$usernameFieldName",
[name: "${username.toLowerCase()}%"],
[area: "user.area"],
[max: params.max])
Also tried changing the param as below:
[area: user.area]
The controller is building an HQL query, so you can't just say "WHERE u.area = userArea", you'll need to use a named parameter and put the value in the queryParams map
def user = springSecurityService.currentUser
def hql = new StringBuilder('FROM ').append(lookupUserClassName()).append(
' u WHERE u.area = :userArea ')
def queryParams = [userArea:user.area]
For the second part of the problem (the Ajax bit), I doubt you need the LOWER conversion, and also you need to put all your query parameters into one map (the second map parameter is just for the pagination settings):
def results = lookupUserClass().executeQuery(
"SELECT DISTINCT u.$usernameFieldName " +
"FROM ${lookupUserClassName()} u " +
"WHERE LOWER(u.$usernameFieldName) LIKE :name AND u.area = :area " +
"ORDER BY u.$usernameFieldName",
[name: "${username.toLowerCase()}%", area:user.area],
[max: params.max])
If you really do want the area check to be case-insensitive then leave it as LOWER(u.area) = :area but then you also need to convert the value you are testing against to lower case:
[name: "${username.toLowerCase()}%", area:user.area.toLowerCase()],

Field Validation in Admin when field are dependent on other fields

How can i apply validation in admin on various fields when they are dependent on each other ?
e.g. Let say in i have a Field A(BooleanField) and Field B (CharField) what i want to do is if in admin user select the Field A(checkbox) and does not enter anything in Field B
and if he tries to save ,it should throw an error like a normal blank=False gives. So how can i do this kind of validation in admin .
E.g Use Case
I have a table having the following structure :-
INTERVIEW_TYPES = (
('default', 'None'),
('Paired Visit','Paired Visit'),
('Time Series', 'Time Series'),
),
class Interview(models.Model):
ic_number = models.CharField(verbose_name ="Visit Configuration Number",max_length=20,unique=True,null =True,blank=True)
ic_description = models.TextField(verbose_name ="Visit Configuration Description",null = True,blank=True)
title = models.CharField(verbose_name ="Visit Configuration Title",max_length=80,unique=True)
starting_section = models.ForeignKey(Section)
interview_type = models.CharField(verbose_name = "Mapped Visit",choices=CHOICES.INTERVIEW_TYPES, max_length=80, default="Time Series")
select_rating = models.CharField(choices=CHOICES.QUESTION_RATING, max_length=80, default="Select Rating")
view_notes = models.CharField(choices=CHOICES.VIEW_NOTES, max_length=80, default="Display Notes")
revisit = models.BooleanField(default=False)
.....and so on ......
class Meta:
verbose_name = 'Visit Configuration'
verbose_name_plural = 'Visit Configurations'
# ordering = ('rpn_number',)
def __unicode__(self):
return self.title
Its admin.py
class InterviewAdmin(admin.ModelAdmin):
list_display = ('id','title', 'starting_section','ic_number','show_prior_responses')
raw_id_fields = ('starting_section',)
admin.site.register(Interview, InterviewAdmin)
In admin , If i select the checkbox of revisit and in the field interview_type(which will show a dropdown having choices None,Paired Visit , Time Series) if a User has selected None from that dropdown and then press save button it should throw me an error like a normal blank=False shows, saying "This field is required"
How can i do this kind validation where fields are dependent on each other ?
Please Ignore syntax error is any .
Thanks
I got confused in response_change and overriding clean method finally this is what i did
override clean method by making a model form in admin.py
class InterviewAdminForm(forms.ModelForm):
class Meta:
model = Interview
def clean(self, *args, **kwargs):
cleaned_data = super(InterviewAdminForm, self).clean(*args, **kwargs)
if self.cleaned_data['interview_type'] == "default" \
and self.cleaned_data['Revisit'] == True:
raise forms.ValidationError({'interview_type': ["error message",]})
return cleaned_data
class InterviewAdmin(admin.ModelAdmin):
# call the form for Validation
form = InterviewAdminForm
#....and so on ....

Django Admin: Many-to-Many listbox doesn't show up with a through parameter

I have the following models:
class Message(models.Model):
date = models.DateTimeField()
user = models.ForeignKey(User)
thread = models.ForeignKey('self', blank=True, null=True)
...
class Forum(models.Model):
name = models.CharField(max_length=24)
messages = models.ManyToManyField(Message, through="Message_forum", blank=True, null=True)
...
class Message_forum(models.Model):
message = models.ForeignKey(Message)
forum = models.ForeignKey(Forum)
status = models.IntegerField()
position = models.IntegerField(blank=True, null=True)
tags = models.ManyToManyField(Tag, blank=True, null=True)
In the admin site, when I go to add/change a forum, I don't see the messages listbox as you'd expect. However, it shows up if I remove the 'through' parameter in the ManyToManyField declaration. What's up with that? I've registered all three models (plus Tag) to the admin site in admin.py.
TIA
Documentation says:
When you specify an intermediary model using the through argument to a
ManyToManyField, the admin will not display a widget by default.
But it's probably possible to display M2M fields in the admin change view even if the through attribute is defined.
class ForumAdminForm(forms.ModelForm):
mm = forms.ModelMultipleChoiceField(
queryset=models.Message.objects.all(),
widget=FilteredSelectMultiple(_('ss'), False, attrs={'rows':'10'}))
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
initial = kwargs.setdefault('initial', {})
initial['mm'] = [t.service.pk for t in kwargs['instance'].message_forum_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self, commit)
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
messages = [s for s in self.cleaned_data['ss']]
for mf in instance.message_forum_set.all():
if mf.service not in messages:
mf.delete()
else:
messages.remove(mf.service)
for message in messages:
Message_forum.objects.create(message=message, forum=instance)
self.save_m2m = save_m2m
return instance
class Meta:
model = models.Forum
class ForumAdmin(admin.ModelAdmin):
form = ForumAdminForm
Take a look at the official documentation:
I learned a lot from #Fedor's answer, but some comments and cleanup may be still beneficial.
class ForumAdminForm(forms.ModelForm):
messages = forms.ModelMultipleChoiceField(
queryset=Message.objects.all(),
widget=FilteredSelectMultiple('Message', False))
# Technically, you don't need to manually set initial here for ForumAdminForm
# However, you NEED to do the following for MessageAdminForm
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
# a record is being changed. building initial
initial = kwargs.setdefault('initial', {})
initial['messages'] = [t.message.pk for t in kwargs['instance'].message_forum_set.all()]
super(ForumAdminForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
if not self.is_valid():
raise HttpResponseForbidden
instance = super(ForumAdminForm, self).save(self, commit)
def save_m2m_with_through():
messages = [t for t in self.cleaned_data['messages']
old_memberships = instance.message_forum_set.all()
for old in old_memberships:
if old.message not in messages:
# and old membership is cleaned by the user
old.delete()
for message in [x for x in messages not in map(lambda x: x.message, old_memberships)]:
membership = Member_forum(message=messsage, forum=instance)
# You may have to initialize status, position and tag for your need
membership.save()
if commit:
save_m2m_with_through()
else:
self.save_m2m = save_m2m_with_through
return instance
class Meta:
model = Forum
fields = {'name', 'messages')
There's one caveat: if you have another many-to-many relationship in the models (that is without through), super(ForumAdminForm, self).save(self, commit) will set self.save_m2m in case commit is False. However, calling this would cause an error, because this function also tries to save the many-to-many with through as well. You may need to save all other many-to-many relationship manually, or catch the exception, or else.
Django admin nicely support many-to-many intermediary models that using the through argument .
For example you have these Person and Group models with intermediate Membership model:
models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Now in admin.py file ,
Define an inline class for the intermediate Membership model:
#admin.register(Membership)
class MembershipInline(admin.TabularInline):
model = Membership
extra = 1
And use them in admin views of models:
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
#admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
More info in official docs:
Models,
Admin

django admin - how to override the default <select> view for foreign key?

I have 2 django model
class Location
address = models.CharField(max_length=100)
city = models.CharField(max_length=20)
class Client
location = models.ForeignKey(Location)
name = models.CharField(max_length=100)
And I have the following admin.py
class ClientAdmin(admin.ModelAdmin):
fieldsets = [
('Client Details', {'fields': ['name']}),
('Location Details', {'fields': ['location']}),
]
admin.site.register(Client, ClientAdmin)
When I try to add a Client I like to have a editable "location" fields i.e. address and city text fields. django by default gives a list.. how can I get the fields from the foregin tables?
any way I can do this? I don't wish to register the Location table in the admin.
thanks
You could put an inline table for the location and use the method queryset to handle the field values.
So, this is the ideia of the solution:
class LocationInline(admin.StackedInline):
model = Location
def queryset(self, request):
query = ... #your custom query here
qs = super(LocationInline, self).queryset(request).filter(query)
return qs
class ClientAdmin(admin.ModelAdmin):
inlines = [LocationInline]
fieldsets = [
('Client Details', {'fields': ['name']}),
]
Edit:
There is another way to do it in django 1.1.
class ClientAdmin(admin.ModelAdmin):
fieldsets = [
('Client Details', {'fields': ['name']}),
('Location Details', {'fields': ['location']}),
]
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name = "location":
locations = Location.objects.filter( ... ) #your query
kwargs["queryset"] = Location.objects.filter(query)
return super(ClientAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
The docs are here: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
It's an old question, but answering this can help a lot of other people too.
So, I hope it helps! ;)

Resources