user-interfacedjango-registrationdjango-profiles

Different user profiles with django-profiles & django-registration


My models.py:

USER_TYPES = (                                                                                                                                                                   
    ('D', 'Demo'   ),                                                                                                                                                               
    ('F', 'Free'   ),
    ('P', 'Premium'),                                                                                                                                                        
)                                                                                                                                                                                                                                                                                                                                                                

class BaseProfile(models.Model):                                                                                                                                                 
    user      = models.OneToOneField(User, primary_key=True)                                                                                                                     
    user_type = models.CharField(max_length=1, blank=True, choices=USER_TYPES)                                                                                                           

class DemoProfile(models.Model):                                                                                                                                                 
    user      = models.OneToOneField(User, primary_key=True)                                                                                                                     
    demo      = models.CharField(max_length=10, blank=True) 
    ...

class FreeProfile(models.Model):                                                                                                                                                 
    user      = models.OneToOneField(User, primary_key=True)                                                                                                                     
    free      = models.CharField(max_length=10, blank=True) 
    ...

class PremiumProfile(models.Model):                                                                                                                                                 
    user      = models.OneToOneField(User, primary_key=True)                                                                                                                     
    premium    = models.CharField(max_length=10, blank=True) 
    ...

class ProxyProfile(BaseProfile):                                                                                                                                                 
    class Meta:                                                                                                                                                                  
        proxy = True             
    def get_profile(self):                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
        if self.user_type == 'D':                                                                                                                                              
            return DemoProfile._default_manager.get(user__id__exact=self.user_id)                                                                                              
        elif self.user_type == 'F':                                                                                                                                            
            return FreeProfile._default_manager.get(user__id__exact=self.user_id)                                                                                              
        else:                                                                                                                                                                  
            return PremiumProfile._default_manager.get(user__id__exact=self.user_id)                                                                                                         

I use BaseProfile to map user_id to specific user_type. I wanted to use ProxyProfile as proxy which loads user_type depending profiles to ModelForm as shown below

Content of my forms.py:

class ProfileForm(ModelForm):                                                                                                                                                    
...                                                                                                                                                                                 
    class Meta:                                                                                                                                                                 
        model   = ProxyProfile                                                                                                                                                  
        exclude = ('user','user_type')   
...

ProfileForm is provided to django-profiles using following code in urls.py:

urlpatterns += patterns('',                                                                                                                                                      
    url(r'^profiles/edit/', edit_profile,                                                                                                                                        
        {'form_class': ProfileForm},                                                                                                                                             
        name='profiles_edit_profile'),                                                                                                                                           
    (r'^profiles/',include('profiles.urls')),                                                                                                                                    
)

I've also set in settings.py:

AUTH_PROFILE_MODULE = 'main.ProxyProfile'

During user registration all db data is filled correctly (it looks like everything is OK). I register using form passed to django-registration:

urlpatterns += patterns('',                                                                                                                                                      
    url(r'^register/$', register,                                                                                                                                                
        {'form_class': UserRegistrationForm},                                                                                                                                    
        name='registration.views.register'),                                                                                                                                     
    (r'', include('registration.urls')),                                                                                                                                         
) 

from forms.py:

class UserRegistrationForm(RegistrationFormUniqueEmail, RegistrationFormTermsOfService):                                                                                         
    utype        = forms.ChoiceField(choices=USER_CHOICES)                                                                                               

    def save(self, profile_callback=None):                                                                                                                                       
        new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
                                                                    password.self.cleaned_data['password1'],                                                                     
                                                                    email=self.cleaned_data['email'],                                                                            
                                                                    )                                                                                                            
        new_base_profile = BaseProfile(user=new_user, user_type=self.cleaned_data['utype'])                                                                                      
        if self.cleaned_data['utype'] == "D":                                                                                                                                    
            new_profile = DemoProfile(user=new_user)                                                                                                                             
        if self.cleaned_data['utype'] == "F":                                                                                                                                    
            new_profile = FreeProfile(user=new_user)                                                                                                                             
        if self.cleaned_data['utype'] == "P":                                                                                                                                    
            new_profile = PremiumProfile(user=new_user)                                                                                                                             
        new_profile.save()                                                                                                                                                       
        new_base_profile.save()                                                                                                                                                  
        return new_user                

And registration phase works OK.

I've problem with profile edit/details pages. My profiles filtered in ProxyProfile model and used as FormModel in ProfileForm are not rendered (I can't see profile specific fields are not rendered to HTML page) Maybe there is some other way (more like Django way :)) to do this (select and render profile model depending on user_type field which is related to User model).

Thanks in advance :)


Solution

  • Ok, finally I've had an idea how I can do this :)

    In my models.py:

    class BaseManager(models.Manager):                                                                                                                                               
        def get(self, **kwargs):                                                                                                                                                     
            self.u = kwargs['user__id__exact']                                                                                                                                       
            self.bt = BaseProfile.manager.get(user__id__exact=self.u)                                                                                                                
            if self.bt.user_type == 'F':                                                                                                                                             
                return FreeProfile.objects.get(pk=self.u)                                                                                                                            
            elif self.bt.user_type == 'I':                                                                                                                                           
                return PremiumProfile.objects.get(pk=self.u)                                                                                                                            
            else:                                                                                                                                                                    
                return None                                                                                                                                                          
    
    class BaseProfile(models.Model):                                                                                                                                                 
        objects   = BaseManager()                                                                                                                                                    
        manager   = UserManager()                                                                                                                                                    
        user      = models.OneToOneField(User, primary_key=True)                                                                                                                     
        user_type = models.CharField(max_length=1, blank=True, choices=USER_TYPES)                                                                                                   
    
    class FreeProfile(models.Model):                                                                                                                                                 
        user      = models.OneToOneField(User, primary_key=True)                                                                                                                     
        free      = models.CharField(max_length=10, blank=True) 
        ...
    
    class PremiumProfile(models.Model):                                                                                                                                                 
        user      = models.OneToOneField(User, primary_key=True)                                                                                                                     
        premium    = models.CharField(max_length=10, blank=True) 
        ...
    

    In custom manager - BaseManager I return profile object by overwriting get() method used by get_profile. I have to use UserManager named simply 'manager' to prevent recursive call of custom manager when assigning self.bt

    OK, this is a half way to achive what I want, now I can view different profiles attached to users using django-profiles app.

    Next, I want to use ModelForm to prepare edit form for user profiles. Users can have different profiles so I've applied the magic trick presented in this snippet: http://djangosnippets.org/snippets/2081/

    And now in my forms.py I have:

    class FreeForm(forms.ModelForm):                                                                                                                                                 
        class Meta:                                                                                                                                                                  
            model = FreeProfile                                                                                                                                                      
    
    
    class PremiumForm(forms.ModelForm):                                                                                                                                                 
        class Meta:                                                                                                                                                                  
            model = PremiumProfile         
    

    next, simple model forms for each profile are assembled in ProfileForm:

    class ProfileForm(ModelForm):                                                                                                                                                    
        def __init__(self, *args, **kwargs):                                                                                                                                        
        self.user = kwargs['instance'].user                                                                                                                                     
        profile_kwargs = kwargs.copy()                                                                                                                                          
        profile_kwargs['instance'] = self.user                                                                                                                                  
        self.bt = BaseProfile.manager.get(user__id__exact=self.user.id)                                                                                                         
        if self.bt.user_type == 'F':                                                                                                                                            
            self.profile_fields = FreeForm(*args, **profile_kwargs)                                                                                                             
        elif self.bt.user_type == 'P':                                                                                                                                          
            self.profile_fields = PremiumForm(*args, **profile_kwargs)                                                                                                             
        super(ProfileForm, self).__init__(*args, **kwargs)                                                                                                                      
        self.fields.update(self.profile_fields.fields)                                                                                                                          
        self.initial.update(self.profile_fields.initial) 
    
        class Meta:                                                                                                                                                                                                                                                                                                      
            model = BaseProfile     
    
        def save(self):
            ...
    

    In settings.py:

    AUTH_PROFILE_MODULE = 'main.BaseProfile'
    

    And it works like a charm but I wonder if it is the Django way to achieve support for multiple different profiles using django-profiles? It worries me that I have to use get() few more times before I render profile details or edit form.

    But after 4 days of struggling with Django to get this done finally I can sleep well tonight :)

    Cheers