django newforms tricks
Image you have some sort of user-profile, and you want to automatically render it using newforms. But leave certain fields out.
(i.e. the user may not be able to change the associated user)
so this is the example model:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
_activated = models.BooleanField()
_activation_key = models.CharField(maxlength=40)
_key_expires = models.DateTimeField()
# custom fields
text = models.CharField(maxlength=100, blank=True)
aboutme = models.TextField("About me",blank=True)
ml = models.BooleanField("MailingList")
class Admin:
pass
As you should have noticed, i introduced a naming convention. (All members beginning with an underscore should not displayed to the user). The next step is to write the corresponding view:
def form_callback(f, **args):
if f.name[0] == "_" or f.name == "user":
return None
return f.formfield(**args)
def edit_profile(request):
if not request.user.is_authenticated():
return HttpResponseRedirect("/accounts/login/")
try:
profile = request.user.get_profile()
except UserProfile.DoesNotExist:
# create new Profile for that user
profile = createNewUserProfile(request.user, True)
ProfileForm = newforms.models.form_for_instance(profile, formfield_callback=form_callback)
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
for key in form.clean_data:
profile.__setattr__(key, form.clean_data[key])
profile.save()
request.user.message_set.create(message='data saved')
else:
form = ProfileForm()
return render_to_response('registration/profile.html', {
'form': form,
'username' : request.user.username
}, context_instance=RequestContext(request))
The main part of the field filtering happens in the callback method form_callback and in the correct call in line 15.
You may not use form.save, as some fields are missing. So just go trough every single field and save it.
Hi Thomas
I am trying to get this to work and I am a novice.
What does your 'createNewUserProfile' look like?
Also what does the view pages look like
hi,
the views.py:
# python
import datetime, random, sha
# output
from django.template import Template, Context, loader, RequestContext
from django.http import HttpResponse, HttpResponseRedirect
from django import forms, newforms
# auth
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
# mail
from django.core.mail import send_mail
# shortcuts
from django.shortcuts import render_to_response, get_object_or_404
# forms
from repo.accounts.forms import *
# models
from repo.accounts.models import *
def account_overview(request):
account_base_url = "/accounts"
return render_to_response('registration/account_overview.html',{
'has_account': request.user.is_authenticated(),
'account_base_url' : account_base_url,
'username' : request.user.username,
})
def confirm(request, activation_key):
if request.user.is_authenticated():
return render_to_response('registration/confirm.html', {'has_account': True})
user_profile = get_object_or_404(UserProfile,
activation_key=activation_key)
if user_profile.key_expires < datetime.datetime.today():
return render_to_response('registration/confirm.html', {'expired': True})
user_account = user_profile.user
user_account.is_active = True
user_account.save()
return render_to_response('registration/confirm.html', {'success': True})
def createNewUserProfile(new_user, activated=False):
# Build the activation key for their account
salt = sha.new(str(random.random())).hexdigest()[:5]
activation_key = sha.new(salt+new_user.username).hexdigest()
key_expires = datetime.datetime.today() + datetime.timedelta(2)
# Create and save their profile
new_profile = UserProfile(user=new_user,
_activation_key=activation_key,
_key_expires=key_expires,
_activated=activated)
new_profile.save()
return new_profile
## Send an email with the confirmation link
#email_subject = 'Your new example.com account confirmation'
#email_body = "Hello, %s, and thanks for signing up for an \
#example.com account!\n\nTo activate your account, click this link within 48 \
#hours:\n\nhttp://example.com/accounts/confirm/%s" % (
#new_user.username,
#new_profile.activation_key)
#send_mail(email_subject,
#email_body,
#'accounts@example.com',
#[new_user.email])
def register(request):
if request.user.is_authenticated():
# They already have an account; don't let them register again
return HttpResponseRedirect('/accounts/profile')
if request.POST:
form = RegisterForm(request.POST)
if form.is_valid():
new_user = User.objects.create_user(form.clean_data['username'],
form.clean_data['email'],
form.clean_data['password1'])
new_user.save()
createNewUserProfile(new_user, True)
#request.user.message_set.create(message='Account successfully created!')
return HttpResponseRedirect('/accounts/register/done')
#return render_to_response('registration/register.html', {'created': True})
else:
form = RegisterForm()
return render_to_response('registration/register.html', {'form': form}, \
context_instance=RequestContext(request))
def created(request):
return render_to_response('registration/created.html')
def obj_callback(f, **kwargs):
"tell forms to ignore fields"
if f.name[0] == "_" or f.name == "user":
return None
return f.formfield(**kwargs)
def edit_profile(request):
if not request.user.is_authenticated():
return HttpResponseRedirect("/accounts/login/")
try:
profile = request.user.get_profile()
except UserProfile.DoesNotExist:
# create new Profile for that user
profile = createNewUserProfile(request.user, True)
ProfileForm = newforms.models.form_for_instance(profile, formfield_callback=obj_callback)
if request.method == 'POST':
form = ProfileForm(request.POST)
if form.is_valid():
for key in form.clean_data:
profile.__setattr__(key, form.clean_data[key])
profile.save()
request.user.message_set.create(message='data saved')
else:
form = ProfileForm()
return render_to_response('registration/profile.html', {
'form': form,
'username' : request.user.username
}, context_instance=RequestContext(request))
def created(request):
return render_to_response('registration/created.html')
def loginajax(request):
manipulator = AuthenticationForm(request)
redirect_to = request.POST.get('next', '')
if request.POST:
errors = manipulator.get_validation_errors(request.POST)
if not errors:
if not redirect_to or '://' in redirect_to or ' ' in redirect_to:
redirect_to = '/accounts/profile/'
request.session[SESSION_KEY] = manipulator.get_user_id()
request.session.delete_test_cookie()
return HttpResponse(redirect_to)
else:
return HttpResponse("false")
what template do you want?
Thank you
Nice idea, but it seems like a hack to me. Introducing field naming conventions clashes with managing the field names in the db. It also assumes that you will only use one sort of form for your userprofile. I already have 2 in my app, and havent even started on allowing users to modify their settings.
If you really want to maintain form configuration in the model, class Meta seems a much better place. admin_only = ('user','activated','key_expires')
@hendrik:
i just used this hack, as i did not found the method you propose.
i think the biggest problem of newforms atm is the lack of documentation and real-world examples!
It might be easier to just add "editable=False" to the fields, and newforms won't render it:
change _activated = models.BooleanField() to
activated = models.BooleanField(editable=False)
when i developed this, this function did not work. hopefully it is working now.