OneToOne field in model form

models.py

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=60, blank=True, null=True)
    last_name = models.CharField(max_length=60, blank=True, null=True)
    location = models.CharField(max_length=300, blank=True, null=True)
    email = models.EmailField(max_length=250, blank=True, null=True)

    def __str__(self):
        return self.user.username


@receiver(post_save, sender=User)
def update_user_profile(sender, instance, created, **kwargs):
    if created:
        Profile.objects.create(user=instance)
    instance.profile.save()

forms.py

class UserProfileUpdateForm(forms.ModelForm):

    class Meta:
        model = Profile
        fields = (
            'user',
            'first_name',
            'last_name',
            'location',
            'email',
        )

        widgets = {
            'user': forms.TextInput(attrs={'class': 'form-control'}),
            'first_name': forms.TextInput(attrs={'class': 'form-control'}),
            'last_name': forms.TextInput(attrs={'class': 'form-control'}),
            'location': forms.TextInput(attrs={'class': 'form-control'}),
            'email': forms.EmailInput(attrs={'class': 'form-control'}),
        }

views.py

class UserProfileUpdateView(UpdateView):
    template_name = 'user_profile_update_view.html'
    model = Profile
    context_object_name = 'profile'
    form_class = UserProfileUpdateForm

template

{% extends 'base.html' %}


{% block content %}
<form method="post">{% csrf_token %}

<div class="container mt-5">
        <div class="row justify-content-center">
            <div class="col-lg-5 ">

                {{ form.as_p }}

                <input type="submit" class="btn btn-dark" value="Create profile">

            </div>
            </div>
        </div>
</form>
{% endblock %}

The ‘user’ field in form will have the User model ID but I dont want that. I would want to have a Username there

Briefly:

  • Remove user from the list of fields - you don’t want Django generating that form field
  • Define a field for username in the form.
  • Override the __init__ method to set the field if the form is bound to an object
  • Override the save method to save that field in the related object

Keep in mind that a ModelForm is a form, with the additional functionality of Django creating form fields for you in a more “declarative” mode. So anything you could do in a form, you can also do in a ModelForm. A ModelForm doesn’t remove any functionality from a Form.

1 Like

Im not sure what you mean from the last 2 steps. I mean I dont understand them.

Sorry, I’m kinda rushed this morning - not sure I can do a decent job on this in the next 5 minutes but I’ll try. Feel free to come back at me with further questions.

A ModelForm is designed to work with one model - you’re trying to work with data from two different models, and so you’ll need to add the functionality to work with that second model.

Your form, UserProfileUpdateForm, is a class, descended from ModelForm. This means that when the class is initialized, the __init__ method is called on the class. Since you’re not supplying one in your class, it’s going to execute the one in the parent class. (This is standard Python stuff - if you’re not familiar with it, review the docs on classes in the Python docs.

You can override the default behavior by adding your own __init__ method to your class. (See the 5th example down in the section on Styling Widgets just as an example.) You’ll want to do something similar to that - define your __init__ method, call super().__init__, and then modify your fields as you see fit. In this case, you’ll want to get the data from the object being bound to the form, and use the user field of the model to retrieve the username field from the User object, and set that as the initial value of your form field.

(If you’re not familiar with the difference between bound and unbound forms, see the docs at Bound and unbound forms.

If you’re allowing the username field to be edited, then you’ll want to add a save method to your form class. You’ll call super().save to save the data for the Profile model, then get the User object for that Profile, update the username field, and save the User object.

1 Like

Hey,

No worries, thank you for taking the time to help me.

I coulnt understand what were you all about because I dont really understand things that in-depth. So far I have mostly used GenericView classes and while searching for answers I only found solutions with functions views that I could understand.
I finally got what I wanted with function view classes but now I have never done a redirect with a model primary key in function view. As in CBV you just could, for example: {% url ‘template_name’ model.id %} but I dont know how it works exactly in function views so yet again i am utterly confused…

As always in programming there isnt one solution and I am more familiar with CBV so I would prefer to that same thing in CBV but it just has seemed way harder.

I have to take a break now before I will try to dive in to your answer and understand what you are telling me.
I really appreciate your help and you going in-depth(what seems to me) but this just confuses me most of the times but I try my best to understand id.

I hope you understand

I am understanding some of what you are saying but I have no idea how do I override the default behavior with init as the examples are just updating widgets attributes.

See the similar thread over at Help with many to many formsets - #9 by typonaut
It’s a very close parallel.

Thank you for helping.

I have found a way to get what I wanted.

forms.py


class UserUpdateForm(forms.ModelForm):

    username = forms.CharField(max_length=15,
                               widget=forms.TextInput(attrs={'class': 'form-control', 'id': 'Username'}))

    class Meta:
        model = User
        fields = ('username',)


class UserProfileUpdateForm(forms.ModelForm):

    class Meta:
        model = Profile
        fields = (

            'first_name',
            'last_name',
            'location',
            'email',
        )

        widgets = {
            'first_name': forms.TextInput(attrs={'class': 'form-control'}),
            'last_name': forms.TextInput(attrs={'class': 'form-control'}),
            'location': forms.TextInput(attrs={'class': 'form-control'}),
            'email': forms.EmailInput(attrs={'class': 'form-control'}),
        }

views.py

def profile_update_view(request, pk):

    user = request.user
    user_form = UserUpdateForm(request.POST or None, instance=request.user)

    user_profile_form = UserProfileUpdateForm(request.POST or None, instance=request.user.profile)

    if request.method == 'POST':
        if user_form.is_valid() and user_profile_form.is_valid():

            user.save()
            user.profile.save()

            return redirect('profile_view', user.profile.id)

    context = {
        'user_form': user_form,
        'user_profile_form': user_profile_form
    }

    return render(request, 'user_profile_update_view.html', context)

urls.py

path('profile/edit/<int:pk>', views.profile_update_view, name='profile_update_view'),

template

<form method="post">{% csrf_token %}

<div class="container mt-5">
        <div class="row justify-content-center">
            <div class="col-lg-5 ">
                <div class="form-group ">
                    {{ user_form.username.label_tag }}
                    {{ user_form.username }}

                </div>

                {% for field in user_profile_form %}
                    <div class="form-group">
                        {{ field.label_tag }}
                        {{ field }}
                    </div>
                {% endfor %}





                <input type="submit" class="btn btn-dark" value="Save changes">

            </div>
            </div>
        </div>
</form>

Anyways thank you for trying to help again. I really appreciate it and I am probably going to come back with another error haha.

1 Like

And this is mine which gives me nothing but a headache. Can you give me a hint, please?
How can I fetch attributes from User model using UserProfile’s OneToOneField relationship?

Well I see a lot of stuff in your code, stuff that I dont really know how it works.
I did not see anything about making separate form for the User model and UserProfile model. Maybe you could try make something out of my code and see if it works? Just seems a lot simpler and if it works then you can do your other stuff there.

Edit1.
I think you cant access the ‘info’ field through UserModel so you have to make a custom form. Form for the UserProfile and from there you could get the ‘info’.

Edit2.
Also I see that you havent really described your problem in Stack. Maybe try to focus on that and then I think you should get the right help because I am a beginner.

1 Like