3

I have a list of users or users/roles type. I want to be able to click a link to Update their respective profiles users.

When I perform click in Profile user, I should be able to edit the associated data to respective user according to profile that handle each of them. Each data profile is the simple page or template and manage for the same UpdateView class based view.

In detail my scenario is the following:

I have the User (inherit from AbstractBaseUser) model and here I manage the account data of the all users, this mean all data in common to all users roles or types which I want manage in my application such as:

  • user student
  • user professor
  • user executive

This users/roles type have their own model in where I define the respective fields to each of them. So, I have too, StudentProfile ProfessorProfile and ExecutiveProfile models, of this way:

My User model is:

class User(AbstractBaseUser, PermissionsMixin):

    email = models.EmailField(unique=True)

    username = models.CharField(max_length=40, unique=True)

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    is_student = models.BooleanField(
        default=False,
        verbose_name='Student',
        help_text='Student profile'
    )

    is_professor = models.BooleanField(
        default=False,
        verbose_name='Professor',
        help_text='Professor profile'
    )

    is_executive = models.BooleanField(
        default=False,
        verbose_name='Executive',
        help_text='Executive profile',
    )

    other fields ...

    def get_student_profile(self):
        student_profile = None
        if hasattr(self, 'studentprofile'):
            student_profile = self.studentprofile
        return student_profile

    def get_professor_profile(self):
        professor_profile = None
        if hasattr(self, 'professorprofile'):
            professor_profile = self.professorprofile
        return professor_profile

    def get_executive_profile(self):
        executive_profile = None
        if hasattr(self, 'executiveprofile'):
            executive_profile = self.executiveprofile
        return executive_profile

    def save(self, *args, **kwargs):
    user = super(User,self).save(*args,**kwargs)

    # Creating an user with student profile
    if self.is_student and not StudentProfile.objects.filter(user=self).exists():
        student_profile = StudentProfile(user = self)
        student_slug = self.username
        student_profile.slug = student_slug
        student_profile.save()

    # Creating an user with professor profile
    elif self.is_professor and not ProfessorProfile.objects.filter(user=self).exists():
        professor_profile = ProfessorProfile(user=self)
        professor_slug = self.username
        professor_profile.slug = professor_slug
        professor_profile.save()

    # Creating an user with executive profile
    elif self.is_executive and not ExecutiveProfile.objects.filter(user=self).exists():
        executive_profile = ExecutiveProfile(user = self)
        executive_slug = self.username
        executive_profile.slug = executive_slug
        executive_profile.save()

# I have this signal to get the username and assign to slug field 
@receiver(post_save, sender=User)
def post_save_user(sender, instance, **kwargs):
    slug = slugify(instance.username)
    User.objects.filter(pk=instance.pk).update(slug=slug)

The idea behind of these schema is that when I create and user with is_student field checked the StudentProfile model is used for complete their data.

When I create and user with is_professor field checked the ProfessorProfile model is used for complete their data.

When I create and user with is_executive field checked the ExecutiveProfile model is used for complete their data.

The models for each Profile (Student, Professor and Executive) are such as follow:

class StudentProfile(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    origin_education_school = models.CharField(
        _("origin education institute"), max_length=128
    )

    current_education_school = models.CharField(
        _("current education institute"), max_length=128
    )

    extra_occupation = models.CharField(
        _("extra occupation"), max_length=128
    )

class ProfessorProfile(models.Model):

    CATHEDRAL_PROFESSOR = 'CATHEDRAL'
    RESEARCH_PROFESSOR = 'RESEARCH'
    INSTITUTIONAL_DIRECTIVE = 'DIRECTIVE'


    OCCUPATION_CHOICES = (
        (CATHEDRAL_PROFESSOR, 'Cathedral Professor'),
        (RESEARCH_PROFESSOR, 'Research Professor'),
        (INSTITUTIONAL_DIRECTIVE, 'Institutional Directive'),
    )

    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    occupation = models.CharField(
        max_length=255,
        blank = False,
    )

class ExecutiveProfile(models.Model):

    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE
    )

    slug = models.SlugField(
        max_length=100,
        blank=True
    )

    occupation = models.CharField(
        max_length=255,
        blank = False,
    )

    enterprise_name = models.CharField(
        max_length=255,
        blank = False,
    )

I have the form to each profile update data of this way in my forms.py:

class UserUpdateForm(forms.ModelForm):
    class Meta:
        widgets = {
            'gender':forms.RadioSelect,
        }
        fields = ("username", "email", "is_student",
            "is_professor", "is_executive",)
        model = get_user_model() #My model User


class StudentProfileForm(forms.ModelForm):
    class Meta:
        model = StudentProfile
        fields = ('origin_education_school', 'current_education_school',
            'extra_occupation')


class ProfessorProfileForm(forms.ModelForm):
    class Meta:
        model = ProfessorProfile
        fields = ('occupation',)


class ExecutiveProfileForm(forms.ModelForm):
    class Meta:
        model = ExecutiveProfile
        fields = ('occupation', 'enterprise_name', 'culturals_arthistic',
            'ecological')

In my class based view AccountSettingsUpdateView I update the data related to the model Users, this mean the Account Data

class AccountSettingsUpdateView(LoginRequiredMixin, UpdateView):
    model = get_user_model()
    form_class = forms.UserUpdateForm
    # success_url = reverse_lazy('dashboard')
    context_object_name = 'preferences'

    def get_context_data(self, **kwargs):
        context = super(AccountSettingsUpdateView, self).get_context_data(**kwargs)

        user = self.request.user
        if user.is_student:
            profile = user.get_student_profile()
            context.update({'userprofile': profile})
        elif user.is_professor:
            profile = user.get_professor_profile()
            context.update({'userprofile': profile})
        elif user.is_executive:
            profile = user.get_executive_profile()
            context.update({'userprofile': profile})
        return context

The url for the below view is this

url(r"^preferences/(?P<slug>[\w\-]+)/$",
        views.AccountSettingsUpdateView.as_view(),
        name='preferences'
    ),

This view AccountSettingsUpdateView works OK.

[02/Apr/2017 23:51:17] "GET /accounts/preferences/luisa/ HTTP/1.1" 200 18092

And, in my other view and only in this view I am updating the data related to the profile of each one of these users. this mean the profiles described above:

class AccountProfilesView(LoginRequiredMixin, UpdateView):

    # When I ask for user with Student Profile
    model = StudentProfile
    form_class = forms.StudentProfileForm

    # sending the form to ProfessorProfile 
    second_form_class = forms.ProfessorProfileForm

    # sending the form to ExecutiveProfile 
    third_form_class = forms.ExecutiveProfileForm


    success_url = reverse_lazy('dashboard')
    template_name = 'accounts/student_form.html'

    def get_context_data(self, **kwargs):
        context = super(AccountProfilesView, self).get_context_data(**kwargs)
        user = self.request.user

        if 'form' not in context:
            context['form'] = self.form_class(self.request.GET,
                instance=user)
        if 'form2' not in context:
            context['form2'] = self.second_form_class(self.request.GET,
                instance=user)
        '''
        if 'form3' not in context:
            context['form3'] = self.third_form_class(self.request.GET,
                instance=user)
        '''
        if user.is_student:
            profile = user.get_student_profile()
            context.update({'userprofile': profile})
        elif user.is_professor:
            profile = user.get_professor_profile()
            context.update({'userprofile': profile})
        elif user.is_executive:
            profile = user.get_executive_profile()
            context.update({'userprofile': profile})
        return context 

And the url to this AccountProfilesView view is this

url(r"^profile/(?P<slug>[\w\-]+)/$",
        views.AccountProfilesView.as_view(
            model=ProfessorProfile),
            name='profile'
    ),

Note that in the url i am passing the ProfessorProfile model like parameter, despite that in the view AccountProfilesView body, I am defining the StudentProfile model, but what happen is that in the url the model = ProfessorProfile override to the model = StundentProfile which came from the view.

In this moment if I am use the luisa user with profile StudentProfile and I going to the url http://localhost:8000/accounts/profile/luisa/ the url is not found:

[03/Apr/2017 01:20:25] "GET /accounts/profile/luisa/ HTTP/1.1" 404 1771
Not Found: /accounts/profile/luisa/ 

enter image description here

But If I remove the attribute model=ProfessorProfile that I am passing like parameter in the URL, this mean that my url stay so:

url(r"^profile/(?P<slug>[\w\-]+)/$",      views.AccountProfilesView.as_view(), name='profile')  

The url http://localhost:8000/accounts/profile/luisa/ is OK

[03/Apr/2017 01:28:47] "GET /accounts/profile/luisa/ HTTP/1.1" 200 4469

This happen because in the view persist the model=StudentProfile attribute.

Until this point, if I am use an user with ProfessorProfile named david and I am going to their profile URL, this is not found

Not Found: /accounts/profile/david/
[03/Apr/2017 01:30:19] "GET /accounts/profile/david/ HTTP/1.1" 404 1769

But I add again the attribute model=ProfessorProfile that I am passing like parameter in the URL, such as mentioned above, the url of david profile ProfessorProfile is OK.

[03/Apr/2017 01:33:11] "GET /accounts/profile/david/ HTTP/1.1" 200 4171

enter image description here

The same inconvenient I have with the ExecutiveProfile user type.

According to the previous behavioral, is of this way, which, I am defining the view to ask for the user type roles and render their respective form. But the inconvenient is that in my view AccountProfilesView I cannot pass or indicate more than One model.

I am trying indicate one second model in my AccountProfilesView of this way:

class AccountProfilesView(LoginRequiredMixin, UpdateView):
    model = StudentProfile
    form_class = forms.StudentProfileForm
    second_form_class = forms.ProfessorProfileForm
    third_form_class = forms.ExecutiveProfileForm
    #success_url = reverse_lazy('dashboard')
    template_name = 'accounts/student_form.html'

    def get_context_data(self, **kwargs):
        context = super(AccountProfilesView, self).get_context_data(**kwargs)
        # Indicate one second model
        context['professor_profile'] = ProfessorProfile

But the result is the same

In summary my question is:

In the UpdateView class based view ... How to can I work with multiple model (more precisely three models StudentProfile, ProfessorProfile and ExecutiveProfile) to can render their respective model forms with the order of access to each profile page user?

I want can perform this wuth any amount of ProfileUser that I have.

I unknown if my schema User and ProfileUser model are good of if there is some better alternative of address this challenge.

UPDATE

According to the answer of @Ma0 Collazos their solution works great.

In this moment the goal is can make a combination of the different profiles, and can render the forms of each profile. So, if an user have a is_professor and is_executive profile, can show in their profile view (AccountProfilesView) their forms respective, this mean that when I go to profile user, can I see the fields the forms professor and the fields the form executive

With the order to get this purpose, I add the scenario in where an user have the combination of profiles in my AccountProfilesView such as follow:

class AccountProfilesView(LoginRequiredMixin, UpdateView):
    # All users can access this view
    model = get_user_model()
    template_name = 'accounts/profile_form.html'
    fields = '__all__'

    def get_context_data(self, **kwargs):
        context = super(AccountProfilesView, self).get_context_data(**kwargs)
        user = self.request.user

        if not self.request.POST:
            if user.is_student:
                profile = user.get_student_profile()
                context['userprofile'] = profile
                context['form_student'] = forms.StudentProfileForm()
            elif user.is_professor:
                profile = user.get_professor_profile()
                context['userprofile'] = profile
                context['form_professor'] = forms.ProfessorProfileForm()
            elif user.is_executive:
                profile = user.get_executive_profile()
                context['userprofile'] = profile
                context['form_executive'] = forms.ExecutiveProfileForm()
            elif user.is_student and user.is_professor and user.is_executive:
                student_profile = user.get_student_profile()
                professor_profile = user.get_professor_profile()
                executive_profile = user.get_executive_profile()

                context['student_profile'] = student_profile
                context['professor_profile'] = professor_profile
                context['executive_profile'] = executive_profile

                context['form_student'] = forms.StudentProfileForm()
                context['form_professor'] = forms.ProfessorProfileForm()
                context['form_executive'] = forms.ExecutiveProfileForm()

        return context

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        user = self.request.user
        if user.is_student:
            context['form_student'] = forms.StudentProfileForm(self.request.POST)
        elif user.is_professor:
            context['form_professor'] = forms.ProfessorProfileForm(self.request.POST)
        elif user.is_executive:
            context['form_executive'] = forms.ExecutiveProfileForm(self.request.POST)
        elif user.is_student and user.is_professor and user.is_executive:
            context['form_student'] = forms.StudentProfileForm(self.request.POST)
            context['form_professor'] = forms.ProfessorProfileForm(self.request.POST)
            context['form_executive'] = forms.ExecutiveProfileForm(self.request.POST)

        return super(AccountProfilesView, self).post(request, *args, **kwargs)

    def form_valid(self, form):
        context = self.get_context_data(form=form)
        user = self.request.user
        user = form.save()
        if user.is_student:
            student = context['form_student'].save(commit=False)
            student.user = user
            student.save()
        elif user.is_professor:
            professor = context['form_professor'].save(commit=False)
            professor.user = user
            professor.save()
        elif user.is_executive:
            executive = context['form_executive'].save(commit=False)
            executive.user = user
            executive.save()
        elif user.is_student and user.is_professor and user.is_executive:
            student = context['form_student'].save(commit=False)
            student.user = user
            student.save()
            professor = context['form_professor'].save(commit=False)
            professor.user = user
            professor.save()
            executive = context['form_executive'].save(commit=False)
            executive.user = user
            executive.save()

        return super(AccountProfilesView, self).form_valid(form)

And in my profile form template, I have the following small logic in which the render of the form to each profile of a separate way works, but when I ask if an user have the three profiles is_student, is_professor and is_executive such as is code section at the end of my template, and I going to profile page of this user, the three forms does not renderized:

<form method="POST">
        {% csrf_token %}
        {% if userprofile.user.is_student %}
            {% bootstrap_form form_student %}

        {% elif userprofile.user.is_professor %}
            {% bootstrap_form form_professor %}

        {% elif userprofile.user.is_executive %}
            {% bootstrap_form form_executive %}

        {% elif userprofile.user.is_student and 
            userprofile.user.is_professor and 
            userprofile.user.is_executive %}
            {% bootstrap_form form_student %}
            {% bootstrap_form form_professor %}
            {% bootstrap_form form_executive %}

        {% endif %}    
        <input type="submit" value="Save Changes" class="btn btn-default">
    </form>

Why my three forms, is not possible show them in one form?

Community
  • 1
  • 1
bgarcial
  • 2,915
  • 10
  • 56
  • 123
  • 1
    Not an answer, so I'll just comment that you might want to look into http://django-vanilla-views.org/ – airstrike Apr 03 '17 at 04:03

1 Answers1

2
  1. Make class AccountProfilesView to have an attribute model = get_user_model(), so all users can access this view.

  2. In your get_context_data method define what form is going to be render and make sure to fill this forms with data entered in a POST method

     # NoQA
     if not self.request.POST:
         if user.is_student:
             context['form_student'] = forms.StudentProfileForm()
         elif user.is_professor:
             context['form_professor'] = forms.ProfessorProfileForm()
     ...
     else:
         if user.is_student:
             context['form_student'] = forms.StudentProfileForm(self.request.POST)
         elif user.is_professor:
             context['form_professor'] =    forms.ProfessorProfileForm(self.request.POST)
     ...
    
  3. Then override the form_valid method to save the corresponding forms

     def form_valid(self, form):
         context = self.get_context_data(form=form) 
         user = form.save() 
         # NoQA
         if user.is_student:
             student = context['form_student'].save(commit=False)
             student.user = user
             student.save()
         elif user.is_professor:
             professor = context['form_professor'].save(commit=False)
             professor.user = user
             professor.save()
     ...
    

Answer based on this post

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
  • Your answer is great. Works perfectly. My `AccountProfileView` did stay of this way: https://pastebin.com/t5HqCyCB . There is something that I don't understand. Why in my `AccountProfilesView` I had put the `fields = '__all__'` attribute. I guess because I am interacting with the forms in `post` and `form_valid` operations from my view ... is possible? – bgarcial Apr 03 '17 at 20:19
  • 1
    I usually won't recommend use `'__all__'` in fields, and I cannot understand why you have to to put this attribute, maybe if you explain more specifically the error I could give you a better answer – Mauricio Collazos Apr 03 '17 at 22:46
  • If I remove the `fields` attribute to my `AccountsProfilesView`, I get this error message https://pastebin.com/dSxhmSwF Do you use `fields` attribute but in form classes? – bgarcial Apr 04 '17 at 00:32
  • 1
    Oh, thats easy, ModelFormMixin extends from FormMixin, that requires a form_class to render a object form to inject in template. ModelFormMixin allow you to build a form only specifying the fields of your model, so in order to have a UpdateView working you need to specify either fields or form_class. – Mauricio Collazos Apr 05 '17 at 22:29
  • can check my update in my questions please? My apologies for the inconvenient. – bgarcial Apr 07 '17 at 14:18
  • In that specific case you need to modify a little your logic, but is not a big deal, just make an structure like if a: args – Mauricio Collazos Apr 07 '17 at 17:12
  • 1
    Sorry about that comment, check this pastebin https://pastebin.com/Wa6W3ieF – Mauricio Collazos Apr 07 '17 at 17:19
  • According to your recommendations I've fixed this paste bin https://pastebin.com/75Ht4mRZ and the error when an user have two profiles and it's necessary render two forms is this `'Parameter "form" should contain a valid Django Form.') bootstrap3.exceptions.BootstrapError: Parameter "form" should contain a valid Django Form. [07/Apr/2017 18:28:48] "GET /accounts/profile/milena/ HTTP/1.1" 500 190058'` This is the pic http://i.imgur.com/HJZHwXC.png – bgarcial Apr 07 '17 at 18:41
  • 1
    Be sure tha form_proffesor is defined properly in `get_context_data` – Mauricio Collazos Apr 07 '17 at 19:46
  • Of course, the problem was my `if ... elif` sentences in the `get_context_data`. Already fixed. – bgarcial Apr 08 '17 at 18:49