1

Summary:

u = self.instance.user

in

def save(self, *args, **kwargs):
  u = self.instance.user
  u.first_name = self.cleaned_data['first_name']
  u.last_name = self.cleaned_data['last_name']
  u.save()
  return super(ProfileForm, self).save(*args, **kwargs)

is causing a problem because self.instance doesn't exist. But yet this is how it is done in other examples, where it seems to work. What am I missing?

Read on for more info ->

I am using both django-registration and django-profiles. For the purposes of just getting it to work, I have not added any extra fields to the profile model (the one that extends User). So far it looks like this:

class sumaConnectUser(models.Model):

    user = models.ForeignKey(User)

    def __unicode__(self):
        return self.user.first_name + " " + self.user.last_name

    def get_absolute_url(self):
        return ('profiles_profile_detail', (), { 'username': self.user.username })
    get_absolute_url = models.permalink(get_absolute_url)

My understanding is as of now, my user "profile" should just include the fields that come with the contrib.auth model User. ( first name, last name etc)

In my urls.py, I pass in my custom form for the creation and edit of the profiles-

(r'^profiles/edit', 'profiles.views.edit_profile', {'form_class': ProfileForm, }),
(r'^profiles/create', 'profiles.views.create_profile', {'form_class': ProfileForm, }),                   
(r'^profiles/', include('profiles.urls')),

Finally, here is my profile form-

from suma.sumaconnect.models import sumaConnectUser
from django import forms
from django.contrib.auth.models import User

class ProfileForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
    super(ProfileForm, self).__init__(*args, **kwargs)
    try:
        self.fields['first_name'].initial = self.instance.user.first_name
        self.fields['last_name'].initial = self.instance.user.last_name
    except User.DoesNotExist:
        pass

first_name = forms.CharField(label="First Name")
last_name = forms.CharField(label="Last Name")

class Meta:
  exclude = ('user',)
  model = sumaConnectUser

def save(self, *args, **kwargs):
  u = self.instance.user
  u.first_name = self.cleaned_data['first_name']
  u.last_name = self.cleaned_data['last_name']
  u.save()
  return super(ProfileForm, self).save(*args, **kwargs)

My goal is to allow the user to edit their first name and last name as part of the profile edit, but not their username and password.

I thought about replacing

u = self.instance.user

with

u = User.objects.get(user = self.cleaned_data['username'])

but this would require me to include a username = forms.CharField on the page which I do not want to display. As far as I understand, when I come to the create profile or edit profile page, I should be automatically editing the profile associated with the user which I am logged in as.

By the time I come to this create or edit user page, the user model already exists, but the profile doesn't. Is this the cause of the problem? I think I am misunderstanding something major, and I would greatly appreciate any pointers as to where I am going wrong. Thanks!

dpetters
  • 163
  • 4
  • 15

1 Answers1

0

You can copy fields over if you like, and dual-update them as you have in the above, but that's not the purpose of the profile. It's supposed to handle fields not in the original auth.User, since you shouldn't need to edit that model directly. You mentioned "extending User" above - you aren't actually inheriting from auth.User, are you? I highly don't recommend that.

Update:

See this reference material. It is using the same syntax as you to do user-updating inside the profile.

Update 2:

The example is for edit profile, not create profile. Looking at the code in profile.views.create_profile:

if request.method == 'POST':
    form = form_class(data=request.POST, files=request.FILES)
    if form.is_valid():
        profile_obj = form.save(commit=False)
        profile_obj.user = request.user
        profile_obj.save()

So it saves the form first, then sets user, then saves the profile.

That means you can't set the user values in the form.save, you need to do it in the profile's model.save:

# in sumaConnectUser:

from django.db.models.base import ObjectDoesNotExist
class sumaConnectUser(models.Model):
  ...
  def save( self, *args, **kwargs ):
    s = super(sumaConnectUser,self).save( *args, **kwargs )
    try:
      u = s.user
      u.first_name = self.first_name
      u.last_name  = self.last_name
      u.save()
    except ObjectDoesNotExist:
      pass
    return s

Basically, by looking at the code in profile.views.create_profile, we are positive that it eventually calls save on the profile after it has set user. It might not be the first time, so we need to trap that case and forget about it. Because eventually, it'll get called and trigger our save-to-user code. The upshot is that no matter what forms you have in the future for this user-profile, it'll always save back to the underlying user.

eruciform
  • 7,680
  • 1
  • 35
  • 47
  • I am not inhereting from auth.User, but instead extending it through the ForeignKey field in the profile model. I know you said I shouldn't be dual-updating anything, but it doesn't seem to me like I am. I mean, all I want is for the user to be able to edit the first_name and last_name attributes of the auth.User model. Therefore I include the two fields in the form. Then I assign them to the appropriate fields in the User and save it before saving the whole profile. When you say try using super.save, do you literally mean putting "super.save()" as the first line in the save function? – dpetters Jul 21 '10 at 17:00
  • sorry, was writing shorthand. i meant your super call as written. nothing wrong with dup-ing fields in this particular case, IMHO, otherwise you need to have two forms and/or edit auth.User. – eruciform Jul 21 '10 at 17:05
  • So I tried putting "profile = super(ProfileForm, self).save(*args, **kwargs)" at the very top and then "return profile" at the very bottom of the function, but I still get a "DoesNotExist at /profiles/create" error at the "u = self.instance.user" line. Not even self.instance exists. Does this mean I need to instantiate the profile the first time around? – dpetters Jul 21 '10 at 17:44
  • the super save call returns an instance, try using that as the instance rather than self.instance. then return whatever the super save call gave you, at the bottom... – eruciform Jul 21 '10 at 17:50
  • def save(self, *args, **kwargs): profile = super(ProfileForm, self).save(*args, **kwargs) u = profile.user u.first_name = self.cleaned_data['first_name'] u.last_name = self.cleaned_data['last_name'] u.save() return profile Gives me the same DoesNotExist error. I think instead of trying random things, I should try to really understand what's going on. thanks for your help so far, I really appreciate the time. The first line above saves the profile. This profile should have a user attribute, but doesn't since profile.user fails. right? – dpetters Jul 21 '10 at 18:22
  • yeah, but my guess is that the user isn't created yet. if you look up the user you know it should be (hardcode if necessary) via `User.objects.get(username="foo")` and raise an exception there so you can debug at that point, does the user actually exist? – eruciform Jul 21 '10 at 18:32
  • updated with a reference that shows your original syntax should have worked. looks like it's something outside this chunk of code... – eruciform Jul 21 '10 at 18:41
  • The flow that I go through so far is that I register the user, & then proceed to the profiles/create page. Therefore, when I get to that form, the user already exists. However, its first_name and last_name fields are blank. putting u = User.objects.get(username="foo") instead of u = profile.user returns the right user. Then the code works perfectly. However, I'm hardcoding the name in the gets(). How can I get access to who's profile I'm editing without putting a form on the page for them to select their username? How does the /profiles/create page know who the user is? Is there some context? – dpetters Jul 21 '10 at 18:56
  • Oh and regarding the update - my code is pretty much exactly the same, which is why I am flustered as to what I'm missing – dpetters Jul 21 '10 at 20:11
  • what does your template that posts the form look like? does it explicitly not have the user field in it? the form needs to have the user field in it, just hidden and inaccessible to the user, otherwise it doesn't have the current user pk in it when you post the result later. – eruciform Jul 21 '10 at 20:37
  • Current my template has the following -
    {{form.as_p}}
    the user selection does not show up because I marked that as "excluded" in the form. If I don't mark it as that, then it will show which I don't want.
    – dpetters Jul 21 '10 at 21:16
  • i think i found it. see above. override the profile model's save, not the form's save. – eruciform Jul 21 '10 at 21:35
  • I think I get it. Instead of defining a save in the forms.py, I should do so in the models.py for sumaConnectUser. It would look something like this: def save(self, *args, **kwargs): self.user.save() return super(sumaConnectUser, self).save(*args, **kwargs) Question though, if inside this save method self is an instance of sumaConnectUser, then how do I access the form data that was inputted? At first I thought it automatically got assigned to the right fields by profile_obj = form.save(commit=False) but putting a print self.user.first_name doesnt output anything. hmm – dpetters Jul 21 '10 at 22:44
  • sorry this took forever, it's hard to debug remotely. :-) i posted another example. basically, it doesn't, but you're dual-saving that data anyways, so when eventually it does get saved AND it has the user set (it WILL eventually), then it'll hit the code. – eruciform Jul 22 '10 at 00:51
  • Eruciform, thank you so much for letting me get to the bottom of this. Your final post makes perfect sense, and I will give it a shot in the next few days. While tinkering on my own in between your responses, I went down a slightly different path. I took out the custom save methods from both the models.py and forms.py and instead followed the first answer from http://stackoverflow.com/questions/2934867/problem-in-adding-custom-fields-to-django-registration. This might not be the best approach, cause I had to modify the backend and then change the the create_inactive_user in registration... – dpetters Jul 22 '10 at 02:12
  • ... as well as the create_user in the actual django.contrib.auth models.py code to accept first_name and last_name. As a result my profile will only have custom fields, with the first name and last name being asked for on the initial registration. It seems a little odd to me that the User model has the first/last name attributes but yet doesn't take it in as optional parameters on the create_user by default. Anyways, what I have now works for the time being but Ill prob revert back to your solution because it doesn't require me to meddle with the django code which I shouldn't do. Thanks again! – dpetters Jul 22 '10 at 02:17