1

I've extended the django user with a OneToOneField so I can store address and such.

SiteUser is the model which extends User using a OneToOneField. How can I get fields of both User and SiteUser in a single ModelForm?

Here is the relevant code so far:

class ProfileForm(ModelForm):
    class Meta:
        model = User
        fields = ('username', 'first_name', 'last_name', 'email')


class AddressForm(ModelForm):
    pass

View in question:

def edit_profile(request):
    username = request.user
    user = User.objects.get(username__exact=username)
    profileform_class = ProfileForm

    if request.method == 'POST':
        profileform = profileform_class(data=request.POST, instance=user)
        if profileform.is_valid():
            profileform.save()
            return redirect('profile')
    else:
        profileform = profileform_class(instance=user)

    return render(request, 'edit_profile.html', {
        'user': user,
        'profileform': profileform,
    })

And the two models:

class Product(models.Model):
    order = models.IntegerField(default=0)
    name = models.CharField(max_length=255)
    description = models.TextField()
    image = models.ImageField(upload_to='product-images', default='default.jpg')
    price = models.FloatField()
    slug = models.SlugField(unique=True)


class SiteUser(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    address = models.CharField(max_length=255)
    post_number = models.CharField(max_length=255, default='')
    post_location = models.CharField(max_length=255, default='')

HTML Page I want the forms on:

{% extends 'base.html' %}
{% block title %}
    Rediger {{ product.name }} - {{ block.super }}
{% endblock title %}

{% block content %}
  <h1>Rediger "{{ user }}"</h1>
  <form role="form" action="" method="post">
    {% csrf_token %}
    {{ profileform.as_p }}
    {{ addressform.as_p }}
    <button type="submit">Submit</button>
  </form>
{% endblock content %}
Yash Tewari
  • 760
  • 1
  • 6
  • 19

3 Answers3

2
  • One option is to use inline formsets. Using this, you won't be needing a second ModelForm.

    Inline formsets is a small abstraction layer on top of model formsets. These simplify the case of working with related objects via a foreign key.

    You can find good examples here.

  • Alternatively, if you want to avoid inline formsets and use both ProfileForm and AddressForm under a single <form> tag as you have done in your template, you can do it like this.

    Forms:

    class ProfileForm(ModelForm):
        class Meta:
            model = User
            fields = ('username', 'first_name', 'last_name', 'email')
    
    class AddressForm(ModelForm):
        class Meta:
            model = SiteUser
            exclude = ['user']
    

    Views:

    def edit_profile(request):
        username = request.user
        user = User.objects.get(username__exact=username)
        profileform_class = ProfileForm
        addressform_class = AddressForm
    
        if request.method == 'POST':
            profileform = profileform_class(data=request.POST, instance=user)
            addressform = addressform_class(data=request.POST, instance=user.siteuser)
            if all((profileform.is_valid(), addressform.is_valid())):
                user = profileform.save()
                address = addressform.save(commit=False)
                address.user = user
                address.save()
                return redirect('profile')
        else:
            profileform = profileform_class(instance=user)
            addressform = addressform_class(instance=user.siteuser)
    
        return render(request, 'edit_profile.html', {
            'user': user,
            'profileform': profileform,
            'addressform': addressform,
        })
    
Community
  • 1
  • 1
Yash Tewari
  • 760
  • 1
  • 6
  • 19
  • So this got me closer to what I wanted, thanks! But the problem now is that the addressform does not really know which user it's referring to. Thus the form does not autofill when there already is an address, and it can't save the new address. The "User" model in the forms.py already know which one it is cause there can only be one user. But just referring to "SiteUser" won't make it point to the current user. In other parts of the script I use user.siteuser to get to the different parts of the model if I want to display it. And the "all()" apparently only takes one statement – Andreas Halvorsen Tollånes Jul 03 '16 at 13:12
  • @AndreasHalvorsenTollånes Edited my answer to refer to the corresponding SiteUser model. Corrected the call to all(): you're supposed to pass a list/tuple to it, sorry. Tell me if this works :) – Yash Tewari Jul 03 '16 at 13:26
  • 1
    Yes! It now works, but to autofill the fields you want to add the instance=user.siteuser to the else: addressform = addressform_class(instance=user.siteuser) ^^ – Andreas Halvorsen Tollånes Jul 03 '16 at 13:45
  • @AndreasHalvorsenTollånes Oops, forgot to add it there: done! – Yash Tewari Jul 03 '16 at 13:57
0

I don't know much about forms, but I think you should use the "initial" parameter when instantiating the AddressForm, as exemplified here: https://docs.djangoproject.com/es/1.9/topics/forms/modelforms/#providing-initial-values

So you create your AddressForm class with SiteUser as model, and when you instantiate it in the view, you make it like this:

AddressForm(initial={'user': request.user})

If "username" is not the primary key of the User model, you can get the primary key like this:

User.objects.get(username=request.user).pk

and then give it in the "initial" parameter.

Andres Espinosa
  • 402
  • 7
  • 14
0

model:

class Company(models.Model):
    name = models.CharField(max_length=150)
    description = models.CharField(max_length=150)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)


class Bike(models.Model):
    to_company = models.OneToOneField(Company, on_delete=models.CASCADE, related_name="bike_company")
    name_bike = models.CharField(max_length=150)
    model = models.CharField(max_length=150)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

form.py

class CompanyForm(ModelForm):
    class Meta:
        model = Company
        fields = ("name", "description" )


class BikeForm(ModelForm):
    name_bike = forms.CharField(required=True)
    model = forms.CharField(required=True)

    class Meta(CompanyForm.Meta):
        model = Company

    @transaction.atomic
    def save(self):
        company = super().save(commit=False)
        company.name = self.cleaned_data.get("name")
        company.description = self.cleaned_data.get("description")
        company.save()
        bike = Bike.objects.create(to_company=company)
        bike.name_bike = self.cleaned_data.get("name_bike")
        bike.model = self.cleaned_data.get("model")
        bike.save()
        return company

the relationship is kept in this line:

Bike.objects.create(to_company=company)

here is an example of a different type of user model

models user and type https://gist.github.com/josuedjh3/259b4b3b03ab195637fe2db3c701edd6

FormModel the User and UserCreationForm

https://gist.github.com/josuedjh3/0c26d989552a82d5b252c5bd3fed1054

josue
  • 630
  • 1
  • 10
  • 16