Different user profiles with django-profiles & django-registration - user-interface

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 :)

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

Related

DRF - Add User to a model with ManyToManyField

I am trying to implement a feature to my backend and allow the owner of private "Group" to add other users by their usernames instead of ID's and allow them to add their images to FileField only once after they were added to the model. The code I have so far:
models.py
class Group(models.Model):
group_name = models.CharField(max_length=255)
group_text = models.TextField(max_length=360, blank=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='owner_user', on_delete=models.SET(get_deleted_user), default=1)
created_on = models.DateTimeField(auto_now_add=True, null=True)
shared_to = models.ManyToManyField(UserProfile, blank=True, related_name='shared_to_user', null=True)
def __str__(self):
return self.group_name
def save(self, *args, **kwargs):
super(Group, self).save(*args, **kwargs)
class GroupImage(models.Model):
group_file = models.FileField(blank=True, null=True,
upload_to='media/covers/%Y/%m/%D/')
gallery_group = models.ForeignKey(Group, related_name='images', on_delete=models.CASCADE)
serializers.py
class GroupImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.GroupImage
fields = ('group_file', )
class SharedToSerializer(serializers.ModelSerializer):
class Meta:
model = models.Group
fields = ('shared_to', )
class GroupSerializer(serializers.ModelSerializer):
images = GroupImageSerializer(many=True, read_only=True)
person = SharedToSerializer(many=True, read_only=True)
class Meta:
model = models.Group
fields = ('id', 'group_name', 'group_text', 'person', 'images')
def create(self, validated_data):
images_data = self.context.get('view').request.FILES
owner_id = self.context['request'].user.id
gallery_group = models.Group.objects.create(group_name=validated_data.get('group_name', 'no-
group_name'), group_text=validated_data.get('group_text'), owner_id=1)
for image_data in images_data.values():
models.GroupImage.objects.create(gallery_group=gallery_group,
group_file=image_data)
return gallery_group
views.py
class GroupCreateAPIView(generics.CreateAPIView):
queryset = models.Group.objects.all()
serializer_class = serializers.GroupSerializer
permission_classes = [AllowAny]
So if your only requirement is how to add users by their username and not their id. You should use SlugRelatedField. I also feel your serializer naming convention is quite confusing. Below is the serializer for Group model that can add users to a group.
class GroupSerializer(Serializer):
... other fields here
shared_to = models.SlugRelatedField(queryset = UserProfile.objects.all(), many=True, slug_field="username", allow_empty=True)
So first checkout SlugRelatedField. This basically is used to map to objects using a specific field of that object(username in this case). You will then get all the UserProfile instances in the shared_to field of the validated_data
property of the serializer which you can fetch in create method and add to you group. And then in the file upload api for your group you can check whether this user belongs to the group or not for permission checking.

Saving(create,update) along with foreignkey value from another model which is related user model

It may be a challenging question if you didn't get rightly. Here I have three models in which department model should be created by taking its place name from Place model which is related to the staff model. The Staff Model is in a OneToOneField relationship with User, so when a user creates a department the place name should be passed like HiddenField in HTML . This place name is related to place model with the user with GenericForeignKey. i have created a serializer which is not working as expected, it is returning the place name ,
.
In shortly I want to create a department while place should be selected from current user ID
class Staff(BaseModel):
ROLES = [
('ADMIN', 'Admin'),
('TEACHER', 'Teacher')
]
auth_user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=50)
school_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
school_id = models.PositiveIntegerField()
school = GenericForeignKey('school_content_type', 'school_id')
role = models.CharField(null=True, blank=True, choices=ROLES, max_length=20)
class Places(BaseModel):
name = models.CharField(max_length=50)
code = models.CharField(max_length=12, unique=True)
class Department(BaseModel):
TYPES = [
('ONLINE', 'Online'),
('OFFLINE', 'OfFline')
]
department_type = models.CharField(max_length=15, choices=TYPES)
service_no = models.CharField(max_length=50)
instructions = models.TextField(null=True, blank=True)
place = models.ForeignKey(Places, to_field='code', db_column='place_code', on_delete=models.PROTECT)
SERIALIZERS
class DepartmentCreateSerializer(serializers.ModelSerializer):
place_code=serializers.CharField(read_only=True)
class Meta:
model=Department
fields = ('department_type','service_no','instructions')
def get_place(self, request):
user_id=self.context['request'].user.id
school_id=Staff.objects.get(auth_user_id= user_id).school_id
places_code_name=Places.objects.get(id= school_id).name
class PlacesSerializer(serializers.ModelSerializer):
class Meta:
model = Places
fields = ('id', 'code', 'name')
from places.serializers import PlacesSerializer
class DepartmentCreateSerializer(serializers.ModelSerializer):
place= PlacesSerializer(read_only=True)
class Meta:
model=Department
fields = ('place','service_no','instructions')
def validate(self, attrs):
palce_obj = self.context['request'].user.staff.place()
attrs.update({'place': place_obj})
attrs = super().validate(attrs)
if not attrs.get('place', None):
raise serializers.ValidationError({'place': ["Place required"]})
return attrs

Django REST foreign key issue

Trying to implement and test my own serializer, I have the following issue:
Searched for many question including this error message, I didn't manage to find any solution to my problem.
DRF Foreign key misusage?
ValueError: Cannot assign "'effcad53-bc45-41fa-be43-4f22c0376eb5'": "Product.related_workspace" must be a "Workspace" instance.
The Workspace class:
class Workspace(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_login = models.ForeignKey(
Login,
on_delete=models.PROTECT,
)
description = models.CharField(
max_length=150,
)
def __str__(self):
login = Login.objects.get(pk=self.related_login_id)
return f'{login.username} ({self.description})'
class Meta:
db_table = 'workspaces'
The Product class:
class Product(models.Model):
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
related_workspace = models.ForeignKey(
Workspace,
on_delete=models.PROTECT,
)
code = models.CharField(
max_length=50,
)
description = models.CharField(
max_length=150,
)
class Meta:
db_table = 'products'
unique_together = ('related_workspace', 'code',)
The ProductSerializer class:
class ProductSerializer(serializers.Serializer):
id = serializers.UUIDField(read_only=True)
related_workspace = serializers.UUIDField()
code = serializers.CharField()
description = serializers.CharField()
def create(self, validated_data):
return Product.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.related_workspace = validated_data.get('related_workspace', instance.related_workspace)
instance.code = validated_data.get('code', instance.code)
instance.description = validated_data.get('description', instance.description)
instance.save()
return instance
The script I'm using to test my serializer :
PROJECT_NAME = 'api'
def main():
#
from api.applications.product.models import Product
from api.applications.product.serializers import ProductSerializer
#
# Create a product
#
code = 'P001'
description = f'Product {code}'
#
# This is a valid workspace id!
#
related_workspace = 'effcad53-bc45-41fa-be43-4f22c0376eb5'
#
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
product.save()
#
serializer = ProductSerializer(product)
print('serializer.data:', serializer.data)
if __name__ == '__main__':
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '%s.settings' % PROJECT_NAME)
import django
django.setup()
main()
Any advice of what I'm missing ?
First thing, when you are testing anything in Django its easiest to write test classes in a test module and use manage.py to run them. All that setup logic you are doing is already in manage.py.
python manage.py test my_app.tests.my_test_class
In your serializer you are setting a foreign key to related_workspace = serializers.UUIDField(), instead you want to nest your serializers (docs) so it pulls out the Workspace data, serializes, saves, and returns a Workspace object. You should have two serializers and they should look something like this, I am going to use ModelSerializer.
from rest_framework import serializers
# Workspace Serializer
class WorkspaceSerializer(serializers.ModelSerializer):
class Meta:
model = Workspace
fields = "__all__"
class ProductSerializer(serializers.ModelSerializer):
related_workspace = WorkspaceSerializer()
class Meta:
model = Product
fields = ["id", "related_workspace", "code", "description"]
def create(self, validated_data):
workspace_data = validated_data.pop('related_workspace')
workspace_serializer = WorkspaceSerializer(data=workspace_data)
if workspace_serializer.is_valid(reaise_exception=True):
workspace = workspace_serializer.save()
product = Product.objects.create(related_workspace = workspace, **validated_data)
return product
We want to remove the workspace data, then feed it to its own serializer, if that data is valid, we save the serializer and we get a Workspace object in return. Then we can create our Product object with that returned workspace object.
Searching deeper, my mistake was in the way I was creating my Product instance...
In my test script, I just updated:
product = Product(
code=code,
description=description,
related_workspace=related_workspace,
)
To:
product = Product(
code=code,
description=description,
related_workspace=Workspace.objects.get(pk=related_workspace)
)

DRF - html post options only for user logged in

I have the following serializer:
class WidgetSerializer(serializers.ModelSerializer):
owner = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model=Widget
fields = ('id', 'title', 'description', 'username', 'code', 'owner', 'list')
The problem is that the 'list' field, which is a drop down, gives all lists whereas I only want it to display lists that are owned by the user currently logged in.
Here's the respective models:
class WidgetList(MPTTModel):
name = models.CharField(max_length=100)
description = models.CharField(max_length=1024)
owner = models.ForeignKey('MyUser')
parent = TreeForeignKey('self', null=True, blank=True, related_name='children', db_index=True)
def __str__(self):
return self.name
class MPTTMEta:
order_insertion_by = ['name']
class Widget(models.Model):
title = models.CharField(max_length=100)
description = models.CharField(max_length=1024)
username = models.CharField(max_length=50)
code = models.CharField(max_length=1024)
owner = models.ForeignKey('MyUser', related_name='MyUser_owner')
list = models.ForeignKey('WidgetList')
I am a beginner in django. I hope that I could help.
Just try this
WidgetList.objects.filter(owner=request.user)
I have to limit it through a SlugRelatedField as per the documentation here -
http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield
I then used it like so -
list = serializers.SlugRelatedField(
queryset=WidgetList.objects.filter(owner=3),
many=True,
slug_field='name'
)
All I need to figure out now is to pass the serializers.CurrentUserDefault() in the filter for the queryset, or pass request.user.

Custom user model: What else is needed to be production ready?

I am new to Django and need to use an email address as a username for my app - plus add some custom fields. So I am following the full example in django docs to make a custom user model.
I am very worried about the part in the example that says:
This example illustrates how most of the components work together,
but is not intended to be copied directly into projects for production
use.
I am not sure what else is needed in order to make the user model production ready.
Is the example missing some crucial security features? (I would like my custom model and authentication to be as good as the default Django one.)
Does anyone have an example that is production ready?
In the end, what I did was follow the full example in the django website, and compare my code with the default Django user model. I believe the default Django user model is production ready, and really, the only difference from that and the custom user model is the use of email as username, so I feel quite reassured that my custom user model is production ready.
Here is my code:
in models.py
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser
)
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.core.mail import send_mail
class MyUserManager(BaseUserManager):
def create_user(self, email, password=None):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('Users must have an email address')
user = self.model(
email=self.normalize_email(email),
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password):
"""
Creates and saves a superuser with the given email and password.
"""
user = self.create_user(email,
password=password
)
user.is_admin = True
user.save(using=self._db)
return user
class MyUser(AbstractBaseUser):
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), max_length=254, blank=False, unique=True, db_index=True)
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
is_admin = models.BooleanField(default=False)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
gender_choices = (
('M', 'Male'),
('F', 'Female'),
)
gender = models.CharField(max_length=1, choices=gender_choices, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
city = models.CharField(max_length=50, blank=True)
phone_number = models.CharField(max_length=15, blank=True)
description = models.TextField(blank=True)
work = models.TextField(blank=True)
objects = MyUserManager()
USERNAME_FIELD = 'email'
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
def get_full_name(self):
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
return self.first_name
def email_user(self, subject, message, from_email=None):
"""
Sends an email to this User.
"""
send_mail(subject, message, from_email, [self.email])
def __unicode__(self):
return self.first_name + ' ' + self.last_name
def has_perm(self, perm, obj=None):
return True
def has_module_perms(self, app_label):
return True
#property
def is_staff(self):
return self.is_admin
Also in my forms.py and views.py I used the User model, so I added the following:
from django.contrib.auth import get_user_model
User = get_user_model()

Resources