views:

127

answers:

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!

A: 

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
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
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
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
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
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 profileGives 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
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
updated with a reference that shows your original syntax should have worked. looks like it's something outside this chunk of code...
eruciform
Dpetters
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
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
Current my template has the following - <form method='POST' action="" enctype="multipart/form-data"> {{form.as_p}} <input type='submit' value='Submit'> </form>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
i think i found it. see above. override the profile model's save, not the form's save.
eruciform
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
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
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
'welcome! good luck!
eruciform
... 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