2

I'm on my second Django project. On my first project I used all generic views, with only the most basic forms tied directly to a custom user model using UpdateView.

In this project I'm trying to implement user profile functionality. My custom user model has some extra dummy fields just so I can manipulate the data. This I refer so as the CustomUser model. I also have a UserAddress model containing addresses since a user can have more than one address. I have tried looking for other questions, and I get similar questions, but there is always something missing:

Django class based views - UpdateView with two model forms - one submit

Using Multiple ModelForms with Class-Based Views

Multiple Models in a single django ModelForm?

Django: multiple models in one template using forms

I've spent the last day or two looking for the "Django way" of doing what I want to do and have had no luck. My initial thought was that I could use a single template, fed with two ModelForms wrapped in a single <form> tag. The view would then process each form and update the CustomUser model and create or update the UserAddress models. I have figured out how to mash together the functionality using the base View CBV, but I suspect I'm duplicating a lot of functionality that I could probably find already done in Django. This is my view, where I handle the form instantiating manually, and feed the context.

class UserDetailsView(View):

    def get(self, request):
        user = request.user
        user_basic = CustomUser.objects.get(pk=user.pk)
        basic_form = UserBasicForm(instance=user_basic)

        user_address = UserAddress.objects.get(user=user.pk)   
        billing_address_form = UserAddressForm(instance = user_address)

        context = {'basic_form':basic_form,'billing_address_form':billing_address_form}

        return render(request, 'mainapp/profile.html', context)

My post is the same at this point, as I haven't done the actual validation and saving yet.

class UserBasicForm(forms.ModelForm):

    class Meta(forms.ModelForm):
        model = CustomUser
        fields = (
            'username',
            'first_name',
            'last_name',
            )
        labels = {
            'username':'Username',
            'first_name':'First Name',
            'last_name':'Last Name',
            }

class UserAddressForm(forms.ModelForm):

    class Meta(forms.ModelForm):
        model = UserAddress
        fields = (
            'description',
            'addressee',
            'company',
            'address_1',
            'address_2',
            'city',
            'prov_state',
            'post_zip',
            'country',
            )
        labels = {
            'description':'Address Description',
            'addressee':'Addressee',
            'company':'Company Name',
            'address_1':'Address',
            'address_2':'Address 2',
            'city':'City',
            'prov_state':'Province or State',
            'post_zip':'Postal or Zip Code',
            'country':'Country',
            }

class CustomUser(AbstractUser):
    objects = CustomUserManager()

    def __str__(self):
        return self.email

class UserAddress(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,)

    description = models.CharField(max_length = 256, default='Description')

    addressee = models.CharField(max_length = 256,)
    company = models.CharField(max_length = 256, default='Self')
    address_1 = models.CharField(max_length = 256,)
    address_2 = models.CharField(max_length = 256,)
    city = models.CharField(max_length = 256,)
    prov_state = models.CharField(max_length = 256,)
    post_zip = models.CharField(max_length = 256,)
    country = models.CharField(max_length = 256,)

    def __str__(self):
        return self.description

Please go easy on me, I'll take most advice offered.

Edit After reviewing some other SO questions and Django form examples, it appears that the final answer probably isn't SO material. That said, my observation is that for the Django built-in CBVs, the "best" base view is that which you can minimize or simplify the code you add. Using a TemplateView or FormView for my project in this case just depends on which methods I choose to re-write or override and for that, I'm still open to suggestions.

Brian
  • 642
  • 7
  • 18
  • did you try to just extend the basic form from the address form class? – dan-klasson Mar 09 '19 at 14:49
  • Hmm, I'm not sure I understand what you mean. A ModelForm and associated methods should work well as the data is tightly coupled to the model (I think). Am I misinterpreting? – Brian Mar 09 '19 at 14:52
  • The view logic is what I feel should likely exist built into Django...the forms and templates I think are project dependent. – Brian Mar 09 '19 at 15:01
  • Yeah should. Try this: https://django-betterforms.readthedocs.io/en/latest/multiform.html#working-with-modelforms – dan-klasson Mar 09 '19 at 15:02
  • I see...I saw another SO answer where they used composite forms, but I couldn't figure out how to make a view work with that. At the time, I felt like a bit of duct tape solution. – Brian Mar 09 '19 at 15:06

1 Answers1

1

I'd do something like this (with betterforms):

class UserCreationMultiForm(MultiModelForm):
    form_classes = {
        'basic': UserBasicForm,
        'address': UserAddressForm,
    }


class UserDetailsView(View):
    template = "mainapp/profile.html"
    form_class = UserCreationMultiForm
    success_url = reverse_lazy('home')

    def form_valid(self, form):
        # Save the user first, because the profile needs a user before it
        # can be saved.
        user = form['basic'].save()
        address = form['address'].save(commit=False)
        address.user = user
        address.save()
        return redirect(self.get_success_url())

Then rename your forms in your template to form.basic and form.address

dan-klasson
  • 13,734
  • 14
  • 63
  • 101
  • So this would use the generic `View` because it contains data from multiple models? – Brian Mar 09 '19 at 16:22
  • After reviewing many more SO questions and answers, I realized I was making a few stupid mistakes that didn't make things any easier. My initial idea to use Django built in views matches other solutions where a main model and form is used in `UpdateView`, and other data is managed manually. For now, I'm looking to avoid multi-form solutions, to keep dependencies down. Your approach uses the generic `view` which is pretty much what I was doing on my previous project. I'll leave this open for now for further input. – Brian Mar 09 '19 at 21:04