Django Forms - Different Versions - python

I am learning django so i looked over a tutorial and then i am looking over another one now. So there are two versions of Signup form i came acrosss.
1st Version:
from django import forms
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
class MyRegForm(UserCreationForm):
email=forms.EmailField(required=True)
class Meta:
model = User
fields =('username','email','password','password2')
def save(self, commit=True):
user=super(UserCreationForm,self).save(commit=False)
if commit:
user.save()
return user
Looking at the first version i was able to understand that i can even make a new form by just making this simple class extending from class like this:
from django import forms
from models import Article
class ArtForm(forms.ModelForm):
class Meta:
model = Article
fields = ('title', 'body', 'pub_data', 'thumb')
2nd Version:
from django import forms
from django.contrib.auth.models import User
class SignupForm(forms.Forms):
username = forms.CharField(max_length=20)
email = forms.EmailField()
pass1 = forms.CharField(max_length=20,widget=forms.PasswordInput(render_value=False))
pass2 = forms.CharField(max_length=20,widget=forms.PasswordInput(render_value==False))
def clean_username(self):
try:
User.objects.get(username=self.cleaned_data['username'])
except User.DoesNotExist:
return self.cleaned_data['username']
raise forms.ValidationError("The Username is already in use. Please Choose Another")
def clean_pass2(self):
if self.cleaned_data['pass2']!=self.cleaned_data['pass1']:
raise forms.ValidationError("The two passwords should match")
return self.cleaned_data['pass2']
def save(self):
new_user = User.objects.create_user(username=self.cleaned_data['username'],
email=self.cleaned_data['email'],
password=self.cleaned_data['pass1'])
return new_user
In this second version we have to setup everything on our own, seems we are having a bit more power being able to add custom validation to it. The save function here seems weird , i dont see in object.save() call in it. It just returns the object.
So which version should be used professionally?
The second version does not have any Meta data in it. I think we can actually mix both kind of versions, but what is missing from both of them and which ones is more generally used?

The second version is a plain form, not a model form. It creates the User object by calling create_user, which instantiates and saves it, so there is no need to call save explicitly.
Generally if you are dealing with model objects, it's usually better to use a ModelForm. In the case of the User object, there is the additional complication that you need to deal with hashing the password - create_user calls user.set_password, which does this. The first version inherits from UserCreationForm which calls set_password directly on the newly-created User instance. If you're creating Users, I would always say to subclass that UserCreationForm if possible.

I'd say the first one is a lot cleaner, but if you need to do special User creation that involves data that can't be attained from the form, or you want to clean your data in a very specific way, it might be worth to use some of the methods from the second, but even with those changes, there is probably not a need to use something other than a ModelForm

Related

What is the correct way to auto-create related model objects in django-registration?

I'm a total Django newbie and apologize in advance if I'm not using the correct terminology.
I'm using django-registration to register user on my web-app. I've successfully adapted it work with my custom user model GeneralUser.
Conceptually, each GeneralUser has a Business, another model that I've defined. Whether this is the correct decision or not, I've decided that I never want to register a user without a related Business object.
I've read countless threads on customizing django form, and finally after a few days of unsuccessful attempts, came upon an answer that helped reach a solution. However, I am unsure that my code is correct/safe. This is my adaptation, followed by the linked-answer:
my adaptation:
class GeneralUserForm(UserCreationForm):
business_name = forms.CharField(required=True)
class Meta:
model = GeneralUser
fields = ['username', 'email', 'password1',
'password2', 'business_name']
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=True)
business = Business(name=user.business_name, owner=user)
# notice: no if-block
user.save()
business.save()
# notice: returning only a user-instance
return user
This code successfully creates a user and a business object, and creates the relationship. Looking at the original answer code though, I wonder if there isn't something critical I'm missing:
Answer I based my code on:
class UserCreateForm(UserCreationForm):
job_title = forms.CharField(max_length=100, required=True)
age = forms.IntegerField(required=True)
class Meta:
model = User
def save(self, commit=True):
if not commit:
raise NotImplementedError("Can't create User and UserProfile without database save")
user = super(UserCreateForm, self).save(commit=True)
user_profile = UserProfile(user=user, job_title=self.cleaned_data['job_title'],
age=self.cleaned_data['age'])
user_profile.save()
# notice: multiple returns
return user, user_profile
A few questions about the differences:
Why doesn't my code work if I end it like this:
.
if commit:
user.save()
business.save()
return user
I'm not using cleaned_data, is that okay?
What is the purpose of the if not commit block in the original code?
Most importantly, is this a "legitimate way" to handle user registration that requires an automatic object-relation on creation?
cleaned_data is the dictionary of data after all validation in every field in that form. Now you can decide whether to rely on it or not(preferably you should).So as a pseudocode we could say cleaned_data + errors will be all fields.
commit is used to decide whether it should commit to db(write). From the above code, to add a related model object like profile, the original object(User) has to be created first.Thats why it make force commit.
To add a related object on object creation, there are multiple ways like post_save signals, override model save, override form save etc.So you are using one of good approach, I would say.

In Django, is it possible for superusers to have different required fields than non-superusers?

I know that superusers and regular users are both just django's User objects, but how can I write a custom user class that requires some fields for plain users and doesn't require those fields for superusers?
No structure in the database is tricky. JSONFields for example may prove to be extremely hard to tame when the app grows.
I would go and try to make it "simple" - more maintainable (I imagine if you need to do stuff like that you may want to extend the model in the future). If this is a new project you can easily change the default user model. But that may or may not help you with your case.
You can always make two models:
from django.db import models
from django.contrib.auth.models import AbstractBaseUser
class Mortal(AbstractBaseUser):
is_superuser = False
username = models.CharField(max_length=256)
first_name = models.CharField(max_length=256)
last_name = models.CharField(max_length=256)
class Admin(AbstractBaseUser):
is_superuser = True
username = models.CharField(max_length=256)
and then make your own authentication backend:
class MyBackend(object):
"""
Danger! A backend to authenticate only via username
"""
def authenticate(self, username=None):
try:
return Mortal.objects.get(username=username)
except Mortal.DoesNotExist:
try:
return Admin.objects.get(username=username)
except Admin.DoesNotExist:
return None
You can have a profile class (say UserProfile) with foreign key to the user that is to be created only when user signs up using the website's registration form. That way, superuser which is created on admin site or through command line wouldn't need an extra profile instance attached to it.

not saving custom fields to django-allauth - no luck from previous posts

I am trying to create custom fields for users to enter on signup with django-allauth. I have referred to several posts about this, but I am not able to get my custom form to save to my database. I do get a combined form on my signup.html page with username, password1 and 2, email and my extra fields of city and school, but I am not able to save the extra fields to the database. I have run syncdb and can see my User Profile table in the admin area.
This advice is the closest I have come to the answer but I do not understand how to implement it: "You can't use UserProfileForm to the allauth.SIGNUP_FORM_CLASS. You need to extend it from SignUpForm and write a save method which will accept the newly created user as the only parameter," from this post:
Custom registration form for use with django-allauth
I have also attempted to integrate advice on this form these posts:
Django Allauth not saving custom form
How to customize user profile when using django-allauth
This is my code:
Models.py
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
# A required line - links a UserProfile to User.
user = models.OneToOneField(User)
# The additional attributes we wish to include.
school = models.CharField(max_length=128)
city = models.CharField(max_length=128)
def __unicode__(self):
return self.user.username
Forms.py
from django import forms
from django.contrib.auth.models import User
from myapp.models import UserProfile
from django.forms.widgets import HiddenInput
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('city', 'school')
def signup(self, request, user):
user=User.objects.get(email=request.email)
city=request.POST.get('city','')
school=request.POST.get('school','')
userprofile_obj = UserProfile(user=user,city=city,school=school)
userprofile_obj.save()
Settings.py
ACCOUNT_SIGNUP_FORM_CLASS = 'myapp.forms.UserProfileForm'
My template is the basic Signup.html from the django-allauth templates and I do not have a view made for this, although I attempted to make one from the tangowithdjango user authentication section register view, and this gave similar behavior (not saving to the database).
Thanks,
Kelly
Not sure if this is still an active question/issue for the original poster: if so, and for anyone else who comes across this, a few things to correct to at least move in the right direction:
I don't see an __init__() method that calls the superclass? E.g.:
def __init__(self, *args, **kwargs):
super(SignupForm, self).__init__(*args, **kwargs)
use the user parameter to the signup method. It should be populated; don't reload it.
Ensure the two objects are linking correctly (I didn't use Django to build my profile table so YMMV but I set user.profile = Profile(...); then execute user.profile.save() at the end of my signup() method.
get the values to place into the profile from the form cleaned_data (e.g. self.cleaned_data['city'] not the POST.
Then start debugging: is your signup() method firing? What does it get? What happens when you execute the profile.save() method?

Django authentication with modelform

I have two models
from django.contrib.auth.models import User
class Vendor(models.MOdel):
user = models.ForeignKey(User,related_name = 'vendor')
......................................................
......................................................
class Customer(models.MOdel):
user = models.ForeignKey(User,related_name = 'customer')
........................................................
.......................................................
what i want to do is enable login for a Vendor and a customer.The login url of vendor is 'vendor/login' and customer is 'customer/login'.While a vendor submit his credentials,i want to check whether the user is a vendor or not and raise a validation error.I couldn't find a way to accomplish this using django.What i basically need is something like a modelform for User which checks the user is a vendor using a queryset.But django auth doesn't have something like this.
The best strategy for me is to write two Authentication Backends, one that enables the vendor authentication and another that does it for the customer.
AUTHENTICATION_BACKENDS is the settings constant giving the authorization backend tuple.
here can you check the documentation about authentication backends.
Declare your implentations with the settings parameter. The order counts django will try all backends untill one is successful.
You could use Django custom user model, add the type field and use it as a factory source to load Vendor/Customer objects. Such solution worked for me very well. Something to start from:
models.py
from django.contrib.auth.models import AbstractUser
TYPES = (
('Vendor', 'Vendor'),
('Customer', 'Customer'),
)
class MyUser(AbstractUser):
type = models.CharField(max_length=10, choices=TYPES, default='Customer')
This approach is very flexible and scales well if you need to add other types in the future. Small example of redirection approach:
class Base():
def __init__(self, auth_user):
self.user = auth_user
def redirect_to(self):
return ""
class Vendor(Base):
def redirect_to(self):
return "/login/vendor"
class Customer(Base):
def redirect_to(self):
return "/login/customer"
Then in the login view you would just dynamically create the user object:
auth_user = form.get_user()
cls = globals()[auth_user.type]
user = cls(auth_user) # This will return either Vendor or Customer object
return HttpResponseRedirect(user.redirect_to())
You can easily create another user types by just adding new class and implementing the needed methods without even touching the rest of the code.

django-admin - how to modify ModelAdmin to create multiple objects at once?

let's assume that I have very basic model
class Message(models.Model):
msg = models.CharField(max_length=30)
this model is registered with admin module:
class MessageAdmin(admin.ModelAdmin):
pass
admin.site.register(Message, MessageAdmin)
Currently when I go into the admin interface, after clicking "Add message" I have only one form where I can enter the msg.
I would like to have multiple forms (formset perhaps) on the "Add page" so I can create multiple messages at once. It's really annoying having to click "Save and add another" every single time.
Ideally I would like to achieve something like InlineModelAdmin but it turns out that you can use it only for the models that are related to the object which is edited.
What would you recommend to use to resolve this problem?
This may not be exactly what you are looking for, but if you want to create multiple objects at the same time you could to somehthing like this:
#In /forms.py
MessageAdminForm(forms.ModelForm):
msg = CharField(max_length=30)
count = IntegerField()
#In /admin.py
from app.admin import MessageAdminForm
MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
fieldsets = (
(None, {
'fields' : ('msg','count')
}),)
def save_model(self, request, obj, form, change):
obj.msg = form.cleaned_data['msg']
obj.save()
for messages in range(form.cleaned_data['count']):
message = Message(msg=form.cleaned_data['msg'])
message.save()
Basicly what you are doing is creating a custom form for your admin template, which ask the user how many times the object shall be created. The logic is than interpreted in the save_model method.
As a workaround, Since, It is likely that you have a FK to User, so you could define an InlineModel on the User model.
Otherwise, the easiest approach may be to create a custom admin view since, there isn't a generic admin view that displays and saves formsets.
This is easy if you are using an Inline. Then you could use extra = 10 or however many extra formsets you want. There doesn't seem to be an equivalent for the ModelAdmin.
Of course in your messages model you would need to create a ForeignKey to some sort of message grouping model as another layer of function and to get the multi-formset layout that you are looking for.
For example:
models.py:
class Group(models.Model):
name = models.CharField(max_length=30)
class Message(models.Model):
msg = models.CharField(max_length=30)
grp = models.ForeignKey(Group)
admin.py:
class MessageInline(admin.TabularInline):
model = Message
extra = 10
class GroupAdmin(admin.ModelAdmin):
inlines = [MessageInline]
admin.site.register(Group, GroupAdmin)
This would give you what you want in the Admin view and create grouping (even if you only allow for one group) and the only extra field would be the name in the group model. I am not even sure you would need that. Also I am sure the value for extra could be generated dynamically for an arbitrary value.
I hope this helps!

Categories