Use custom UserCreationForm with GeoDjango admin (OSMGeoAdmin) - django

Django==2.2.1
GDAL==2.3.2
django-username-email==2.2.4
I have a simple Django application with a custom user model based on django-username-email's AbstractCUser, which removes the username from the user model, using e-mail address instead. On the user model, I defined a PointField field storing the user's current location.
models.py
from django.contrib.gis.db import models as gis_models
from cuser.models import AbstractCUser
class User(AbstractCUser):
"""Custom user model that extends AbstractCUser."""
current_location = gis_models.PointField(null=True, blank=True,)
I would like to register this model in Django admin so that I can register new users and view/set their location with a map widget. This kind of works if I use a custom user admin based on admin.OSMGeoAdmin in combination with a custom user change form:
admin.py
from django.contrib.gis import admin
from django.contrib.auth import get_user_model
from .forms import CustomUserCreationForm, CustomUserChangeForm
class CustomUserAdmin(admin.OSMGeoAdmin):
model = get_user_model()
add_form = CustomUserCreationForm # <- there seems to be a problem here
form = CustomUserChangeForm
list_display = ['email', 'last_name', 'first_name']
readonly_fields = ['last_login', 'date_joined']
admin.site.register(get_user_model(), CustomUserAdmin)
forms.py
from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = get_user_model()
exclude = ('username',)
class CustomUserChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = get_user_model()
fields = (
'email',
'first_name',
'last_name',
'current_location',
# ...
)
When I open an existing user record in the Django admin, the required fields are displayed as intended, and the current location is displayed on a map. However, the same form seems to be used for user creation as well (i.e. add_form has no effect), which makes it impossible to add new users via the admin, because the password setting functionality is not embedded correctly (see screen shot).
The problem seems to be that OSMGeoAdmininherits from ModelAdmin, which in contrast to the standard UserAdmindoes not have an add_form property.
Is there any way to specify a custom user creation form in this case (ideally the UserCreationForm provided by django-username-email while maintaining the ability to display point fields on a map on the user change form?

You need to override get_form similar to how django.contrib.auth.admin.UserAdmin does.
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during user creation
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)

Following schillingt's suggestion, this is the code I ended up using:
from django.contrib.gis import admin
from django.contrib.auth import get_user_model
from cuser.forms import UserCreationForm
from .forms import CustomUserChangeForm
class CustomUserAdmin(admin.OSMGeoAdmin):
model = get_user_model()
add_form = UserCreationForm
form = CustomUserChangeForm
list_display = ['email', 'last_name', 'first_name']
readonly_fields = ['last_login', 'date_joined']
def get_form(self, request, obj=None, **kwargs):
"""
Use special form during user creation.
Override get_form method in the same manner as django.contrib.auth.admin.UserAdmin does.
"""
defaults = {}
if obj is None:
defaults['form'] = self.add_form
defaults.update(kwargs)
return super().get_form(request, obj, **defaults)
admin.site.register(get_user_model(), CustomUserAdmin)

Related

Use autocomplete_fields with Proxy model

I want to implement autocomplete_fields feature but it doesn't work. I assume it happens because of Proxy model.
So I have Customer Proxy model and PromoCode model. PromoCode has FK to Customer model. And I need to have search field for customers in PromoCode change form. Here are models and admin classes:
class User(AbstractUser):
# bunch of fields
class Customer(User):
class Meta:
proxy = True
class CustomerAdmin(admin.ModelAdmin):
search_fields = ['email',]
admin.site.register(Customer, CustomerAdmin)
class PromoCode(TimeStampedModel):
customer = models.ForeignKey(User, on_delete=PROTECT, null=True, blank=True)
class PromoCodeAdmin(admin.ModelAdmin):
autocomplete_fields = ('customer',)
admin.site.register(PromoCode, PromoCodeAdmin)
This code gives error:
: (admin.E039) An admin for model "User" has to be registered to be referenced by PromoCodeAdmin.autocomplete_fields.
But I can't change model in customer field to Customer, becase when I run migration it breaks with following error:
ValueError: The field coupons.PromoCode.customer was declared with a lazy reference to 'users.customer', but app 'users' doesn't provide model 'customer'
Also I can't register User as admin class, because I don't need it to be registered. I register Customer model.
What can I do to solve such case?
It is not possible (see: https://code.djangoproject.com/ticket/30666). I got around this by registering the User admin, but making it redirect to my custom admin model. I also removed all of the actions on the user admin:
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.urls import reverse
admin.site.unregister(User)
#admin.register(User)
class UserAdmin(BaseUserAdmin):
preserve_filters = False
def get_actions(self, request):
actions = super().get_actions(request)
if "delete_selected" in actions:
del actions["delete_selected"]
return actions
def has_delete_permission(self, request, obj=None):
return False
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
return False
def changelist_view(self, *args, **kwargs):
return HttpResponseRedirect(reverse("admin:core_domainuser_changelist"))

Django execute custom code after user signup

I have a users app in a Django project (version 2.1 and python 3.6). After an user signup (both front end and when added in the admin dashboard ideally), I'd like to insert data in one other table. I know how to insert data, but I didn't find out how to do it right after a successfull signup.
Ideal answer would just show me how to do something like print('hello') right after an user created his account.
# users/admin.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm, CustomUserChangeForm
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
add_form = CustomUserCreationForm
form = CustomUserChangeForm
model = CustomUser
list_display = ['email', 'username',]
admin.site.register(CustomUser, CustomUserAdmin)
# users/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm):
model = CustomUser
fields = ('username', 'email')
class CustomUserChangeForm(UserChangeForm):
class Meta:
model = CustomUser
fields = ('username', 'email')
# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
# add additional fields in here
credit = models.IntegerField(default=200) # editable=False
def __str__(self):
return self.email
# users/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('signup/', views.SignUp.as_view(), name='signup'),
]
# users/views.py
from django.urls import reverse_lazy
from django.views import generic
from .forms import CustomUserCreationForm
class SignUp(generic.CreateView):
form_class = CustomUserCreationForm
success_url = reverse_lazy('login')
template_name = 'signup.html'
Use a post-save signal
https://docs.djangoproject.com/en/2.1/ref/signals/
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User)
def say_hello(sender, instance, **kwargs):
if instate._state.adding:
print('hello')
Signal is better than a method on the view because the User may be created some way other than through the view e.g , via the shell, a management command, a migration, a different view, etc.
Note the _state is not "private" so don't feel bad about using it, it's just named that way to avoid clashing with field names.
Check _state instead of more common checking instance.pk because instance.pk is always present when primary key is a natural key rather than AutoField
I think the best approach would be overriding save method of CustomUser model. For example:
class CustomUser(AbstructUser):
def save(self, *args, **kwargs):
user = super(CustomUser, self).save(*args, **kwargs)
print("Hello World")
return user
Check here in Django documentation for more details: https://docs.djangoproject.com/en/2.1/topics/db/models/#overriding-predefined-model-methods.

Override a Django generic class-based view widget

Say I have a basic CreateView form, like this, to allow new users to register on a site:
from django.contrib.auth import get_user_model
from django.http import HttpResponse
from django.views.generic import CreateView
User = get_user_model()
class Signup(CreateView):
model = User
fields = ['first_name', 'last_name', 'email', 'password']
I just tried this, and found that the password field is rendered in plain text; how would I go about overriding the view so that it uses forms.PasswordInput() instead? (I realise it's probably easiest to just define the form by hand, but I'm just curious about how you'd do that.)
You could override get_form(), and modify the form to change the widget on the password field:
from django import forms
class Signup(CreateView):
model = User
fields = ['first_name', 'last_name', 'email', 'password']
def get_form(self, form_class):
form = super(Signup, self).get_form(form_class)
form.fields['password'].widget = forms.PasswordInput()
return form
But an even better way would be to just create a custom form class. In the custom class just set widgets on the Meta class. Like this:
from django import forms
class SignupForm(forms.ModelForm):
class Meta:
model = User
fields = ['first_name', 'last_name', 'email', 'password']
widgets = {
'password': forms.PasswordInput()
}
class Signup(CreateView):
form_class = SignupForm
model = User
Usually you would put the custom form class in a forms.py file as well.
Not sure if this affected earlier versions of Django, but in more recent versions the get_form() should have a default form_class=None when overriding that method.
The updated (Python 3, Django 2.2) example would be:
from django import forms
class Signup(CreateView):
model = User
fields = ['first_name', 'last_name', 'email', 'password']
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.fields['password'].widget = forms.PasswordInput()
return form
https://docs.djangoproject.com/en/2.2/ref/class-based-views/mixins-editing/#django.views.generic.edit.FormMixin

Django user-to-groups in userAdmin, and group-to-users in groupAdmin

Is it possible to have many-to-many widget in admin-panel in user-tab to pick groups, that user belong to, and similar many-to-many widget in group-tab to pick users, which should belong to that group?
There's the easy way and the hard way.
The easy way is to use Django's InlineModelAdmin objects. This way, however, you cannot use the group widget.
from django.contrib.auth.admin import GroupAdmin
from django.contrib.auth.models import User, Group
class UserSetInline(admin.TabularInline):
model = User.groups.through
raw_id_fields = ('user',) # optional, if you have too many users
class MyGroupAdmin(GroupAdmin):
inlines = [UserSetInline]
# unregister and register again
admin.site.unregister(Group)
admin.site.register(Group, MyGroupAdmin)
The hard way requires you to build your own form, manually load and save the related users:
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import GroupAdmin
from django.contrib.auth.models import User, Group
class GroupForm(forms.ModelForm):
users = forms.ModelMultipleChoiceField(
label='Users',
queryset=User.objects.all(),
required=False,
widget=admin.widgets.FilteredSelectMultiple(
"users", is_stacked=False))
class Meta:
model = Group
exclude = () # since Django 1.8 this is needed
widgets = {
'permissions': admin.widgets.FilteredSelectMultiple(
"permissions", is_stacked=False),
}
class MyGroupAdmin(GroupAdmin):
form = GroupForm
def save_model(self, request, obj, form, change):
# save first to obtain id
super(GroupAdmin, self).save_model(request, obj, form, change)
obj.user_set.clear()
for user in form.cleaned_data['users']:
obj.user_set.add(user)
def get_form(self, request, obj=None, **kwargs):
if obj:
self.form.base_fields['users'].initial = [o.pk for o in obj.user_set.all()]
else:
self.form.base_fields['users'].initial = []
return GroupForm
# unregister and register again
admin.site.unregister(Group)
admin.site.register(Group, MyGroupAdmin)

Django User model email field: how to make it mandatory

I need to make the email field in the Django User model mandatory. It isn't obvious to me how to do that. Suggestions welcome. I am currently using:
from django.contrib.auth.forms import UserCreationForm
for my User creation form, and combining this with my own custom UserProfileCreateForm
Ian
You should be able subclass the provided registration form and override properties of a field in the Meta class.
from django.contrib.auth.forms import UserCreationForm
# Not sure about the syntax on this one. Can't find the documentation.
class MyUserCreationForm(UserCreationForm):
class Meta:
email = {
'required': True
}
# This will definitely work
class MyUserCreationForm(UserCreationForm):
def __init__(self, *args, **kwargs):
super(MyUserCreationForm, self).__init__(*args, **kwargs)
self.fields['email'].required = True
from django import forms
from django.contrib.auth.models import User
class MyUserForm(forms.ModelForm):
email = forms.CharField(max_length=75, required=True)
class Meta:
model = User
fields = ('username', 'email', 'password')
use EmailField in your model
see more at
https://docs.djangoproject.com/en/2.1/ref/models/fields/#emailfield