I am trying to create an intranet/extranet with internal/external user-specific profiles, with a common generic profile. I've looked at several answers on this site, but none specifically address what I'm looking to do. Below are the (stripped down) files I have so far.
What's the best way to create a profile model, with subprofiles for each user type? I'm trying not to require a custom authentication backend if at all possible.
https://gist.github.com/1196077
I have a solution I dont Know if its the best but see it:
models.py
from django.db import models
from django.contrib.auth.models import User
class Pollster(models.Model):
"""docstring for Polister"""
user = models.OneToOneField(User, related_name = 'polister', unique=True)
cedule = models.CharField( max_length = 100 )
class Respondent(models.Model):
""" """
born_date = models.DateField( verbose_name=u'fecha de nacimiento' )
cedule = models.CharField( max_length = 100, verbose_name=u'cedula' )
comunity = models.CharField( max_length = 100, verbose_name=u'comunidad')
phone = models.CharField( max_length = 50, verbose_name=u'telefono')
sanrelation = models.TextField( verbose_name =u'Relacion con SAN')
user = models.OneToOneField( User, related_name = 'respondent')
I create a MiddleWare: so
i create middleware.py
from django.contrib.auth.models import User
from encuestas.models import Pollster, Respondent
class RequestMiddleWare(object):
"""docstring for """
def process_request(self,request):
if isPollster(request.user):
request.user.userprofile = Pollster.objects.get( user = request.user.id)
elif isRespondent(request.user):
request.user.userprofile = Respondent.objects.get(user = request.user.id)
return None
def isPollster(user):
return Pollster.objects.filter(user=user.id).exists()
def isRespondent(user):
return Respondent.objects.filter(user=user.id).exists()
and you need to configure settings.py for the middleware:
add to MIDDLEWARE_CLASSES atribute:
'encuestas.middleware.RequestMiddleWare'
encuestas is my_app name
middleware is the Middleware file
RequestMiddleWare is the middleware class
You need a combination of storing additional information about users and model inheritance.
Basically, you'll need the generic User models we all know and either love or hate, and then you need a generic profile model that is your AUTH_PROFILE_MODULE setting.
That profile model will be a top-level model, with model subclasses for internal and extrernal users. You probably don't want an abstract model in this case since you'll need a common profile table to load user profiles from with User.get_profile().
So...I think the major thing you want to change is to make your Associate, External, etc. models inherit from your Profile model.
Please check this excellent article that describes how to inherit from the User class and add your own information. For me, at least, this clearly seems to be the way to go: http://scottbarnham.com/blog/2008/08/21/extending-the-django-user-model-with-inheritance/
Using this method one should easily be able to add multiple user types to their Django application.
Related
I'm actually learning Django Rest Framework and I have this project that involves multiple user types so for example it consists of Students, Teachers and Admins each one with his attributes so I created a User model that inherits from AbstractUser and 3 other models for each type where each type has a foreign key of it's corresponding user. So here comes the problem how can I use Django-Rest-Auth library with these user types so for example when I use the register endpoint how can I modify it in order to create a Student in the students model based on the request data ?
I would appreciate any ones help.
Thanks in advance
I would go with something like this for REST. So in my models,
class CustomUser(AbstractUser):
CHOICES = (
('T', 'Teacher'),
('A', 'Administration'),
('S', 'Student'),
)
role = models.CharField(max_length=1, choices=CHOICES)
class Teacher(models.Model):
user = models.OneToOneField(
CustomUser, on_delete=models.CASCADE, related_name="teacher_account"
)
...
class Student(models.Model):
...
Once your models are up and your settings updated to use the custom AUTH_USER_MODEL, create a custom registration serializer.
from rest_framework import serializers
from allauth.account.adapter import get_adapter
from allauth.account.utils import setup_user_email
from rest_auth.registration.serializers import RegisterSerializer
class MyCustomRegistrationSerializer(RegisterSerializer):
CHOICES = (
('T', 'Teacher'),
('A', 'Administration'),
('S', 'Student'),
)
role = serializers.ChoiceField(max_length=1, choices=CHOICES)
def get_cleaned_data(self):
data_dict = super().get_cleaned_data()
data_dict['role'] = self.validated_data.get('role', '')
return data_dict
Note the difference,serializers.ChoiceField.
Go ahead and tell django to use this during registration.So in your settings.py:
# note the path, use the location of your current serializers
REST_AUTH_REGISTER_SERIALIZERS = {
'REGISTER_SERIALIZER': 'api.serializers.MyCustomRegistrationSerializer',
}
Now, your local path for registering users should show an additional choice filed so that whenever a new user registers, they specify what role they have.
To save this information to the database during registration, however, create an adapter. So in your project application, say app3/adapter.py:
from allauth.account.adapter import DefaultAccountAdapter
class CustomAccountAdapter(DefaultAccountAdapter):
def save_user(self, request, user, form, commit=False):
user = super().save_user(request, user, form, commit)
data = form.cleaned_data
user.preferred_locale = data.get('role')
user.save()
return user
Likewise, register this adapter in your settings.
ACCOUNT_ADAPTER = 'app3.adapter.CustomAccountAdapter'
This should work. Have not tested it out for your particular use case though.
I want to model the following application: an Owner has different Shops and each Shop has some Customers and some Employees working for that Shop; the same Employee can work in different Shops belonging to the same Owner, but also in Shops belonging to different Owners.
Only Owner and Employee can login into the system, Customer can't login.
I created the following models and added users to different Groups (using Django Auth system and version 1.6.2 which allows custom user models), but I'm concerned with the number of query that the application is doing and I'm really not sure about the modeling.
The big difficulty is that, if the Owner has various Shops, when the Owner login into the system he needs to choose which Shop is working with, also to be able to add the related Employees and Customers (only the Owner of the Shop can add Employees and Customers)
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.CharField(max_length=254,
unique=True)
firstname = models.CharField(max_length=64)
lastname = models.CharField(max_length=64)
...
objects = CustomUserManager()
USERNAME_FIELD = 'email'
...
class Shop(models.Model):
name = models.CharField(...)
city = ...
address = ...
class Customer(models.Model):
shop = models.ForeignKey(Shop)
...
class Employee(CustomUser):
shops = models.ManyToManyField(Shop)
...
class Owner(CustomUser):
shops = models.ManyToManyField(Shop)
...
Now, when the Employee or the Owner login into the system with his email, the app needs to show a select box with the available shops, and the choice of the user need to pass to every view of the application: how do I do that? I suppose can't be a POST since I'll have other forms in the app, should be a GET request, but on every request I need to verify is the Shop belongs to the Owner or to the Employee (increasing number of queries). I already developed a big part of the application (order form for example) but I'm coming back to the beginning; I don't know if all the models I've done should be related to the Shop or to the Owner.
Any advice is appreciated. Thanks.
I have solved a similar problem using sessions and custom middleware based on Django's authentication middleware:
shop/middleware.py
from django.utils.functional import SimpleLazyObject
from <appname> import shop
def get_shop(request):
if not hasattr(request, '_cached_shop'):
request._cached_shop = shop.get_shop(request)
return request._cached_shop
class ShopMiddleware(object):
def process_request(self, request):
assert hasattr(request, 'session'), "The Shop middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
request.shop = SimpleLazyObject(lambda: get_shop(request))
shop/__init__.py
from django.core.exceptions import ObjectDoesNotExist
from <appname>.shop.models import Shop
SHOP_SESSION_KEY = '_session_shop_id'
def get_shop(request):
try:
shop_id = request.session[SHOP_SESSION_KEY]
shop = Shop.objects.get(id=shop_id)
return shop
except (KeyError, ObjectDoesNotExist):
return None
def switch_shop(request, shop):
if not isinstance(request.user, CustomUser):
request.session[SHOP_SESSION_KEY] = None
if request.user.shops.filter(id=shop.id).exists():
request.session[SHOP_SESSION_KEY] = shop.id
Then just add ShopMiddleware to your middleware classes, and request.shop will always point to the current shop if one is selected.
In my case I have also written a view wrapper similar to login_required that redirects to a page that allows selection of a shop whenever one is required and not selected. Take a look at login_required's source code for a good pointer in the right direction.
EDIT: You still need to select a shop, so write a view that presents the user with the right options, and let it call switch_shop(request, shop). If the shop is a valid shop for the current user, the session will be set to that shop until it is changed or the user logs out.
I now my example can be unperfect but i think it will clarify how you should use Django for this.
(Also read this: https://docs.djangoproject.com/en/1.6/topics/db/managers/)
class ShopsUser(AbstractBaseUser, PermissionsMixin):
email = models.CharField(max_length=254,
unique=True)
firstname = models.CharField(max_length=64)
lastname = models.CharField(max_length=64)
...
objects = CustomUserManager()
USERNAME_FIELD = 'email'
...
priviledge_flag = models.CharField(choices=(('o', 'owner'), ('e', 'employe'), ('c', 'customer'))
class Customer(models.Model):
shop = models.ForeignKey(Shop)
class Shop(models.Model):
customers = models.ForeignKey(Customer, related_name='shops')
admins = models.ManyToMany(ShopsUser, related_name='managed_shops')
Now you can find all data by using you logged in user (use sessions) in view:
class SomeView(View):
def get(self, *args, **kwargs):
admin = self.request.user
all_singed_in_admin_shops = admin.managed_shops.all()
first_shop = all_singed_in_admin_shops[0]
first_shop_customers = first_shop.customers.all()
I'm trying to use social_auth (omab) for the first time and I'm find that there is no working example how to store basic facebook user data. Authentication works and user is created without problem as it's explained in the social_auth docs but I need to store gender and locale also. Both of them belongs to the basic facebook user data so they are in the facebook response all the time.
I'm use Django 1.4, Python2.7 and latest social_auth. So I was try to use SOCIAL_AUTH_USER_MODEL = 'myapp.UserProfile' in settings.py file and model.py is:
#!/usr/bin/python
#-*- coding: UTF-8 -*-
from django.db import models
from django.contrib.auth.models import User
from django.db.models import signals
import datetime
from datetime import timedelta
from django.db.models.signals import post_save
from social_auth.signals import pre_update
from social_auth.backends.facebook import FacebookBackend
class CustomUserManager(models.Manager):
def create_user(self, username, email):
return self.model._default_manager.create(username=username)
class UserProfile(models.Model):
gender = models.CharField(max_length=150, blank=True)
locale = models.CharField(max_length=150, blank=True)
#social_auth requirements
username = models.CharField(max_length=150)
last_login = models.DateTimeField(blank=True)
is_active = models.BooleanField()
objects = CustomUserManager()
class Meta:
verbose_name_plural = 'Profiles'
def __unicode__(self):
return self.username
def get_absolute_url(self):
return '/profiles/%s/' % self.id
def facebook_extra_values(sender, user,response, details, **kwargs):
profile = user.get_profile()
current_user = user
profile, new = UserProfile.objects.get_or_create(user=current_user)
profile.gender = response.get('gender')
profile.locale = response.get('locale')
profile.save()
return True
pre_update.connect(facebook_extra_values, sender=FacebookBackend, weak = False, dispatch_uid = 'facebook_extra_values_user')
In the settings.py I'm define pipeline
SOCIAL_AUTH_PIPELINE = (
'social_auth.backends.pipeline.social.social_auth_user',
#'social_auth.backends.pipeline.associate.associate_by_email',
'social_auth.backends.pipeline.user.create_user',
'social_auth.backends.pipeline.social.associate_user',
'social_auth.backends.pipeline.social.load_extra_data',
'social_auth.backends.pipeline.user.update_user_details',
'social_auth.backends.pipeline.misc.save_status_to_session',
)
but with above I get error AssertionError: ForeignKey(None) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string 'self'
Also I was try to use AUTH_PROFILE_MODULE = 'myapp.UserProfile' instead as I was do before to extend user.model, which works well but don't understand how to populate needed data when UserProfile is created. Does anyone can place working code for this problem?
Thanks
There are a couple of ways to archive it, what fits better to your project is up to you of course, here's a list of available options:
Define this setting FACEBOOK_EXTRA_DATA = ('gender', 'locale'), the values will be available at the UserSocialAuth instance, to get them just do user.social_auth.get(provider='facebook').extra_data['gender'] or ['locale']. This is possible just because the information is available in the basic user data response.
Use a user profile to store this data (check django doc about it). Then just add a pipeline entry that stores the values in your profile instance.
Create a custom user model, SOCIAL_AUTH_USER_MODEL = 'myapp.CustomUser', and again add a custom pipeline entry that stores the values in your user instance.
Number 1 is not the best solution IMO, since a user can have several Facebook accounts connected and it could create a mess. Number 2 is good for Django 1.4 and lower, but it's deprecated starting from Django 1.5, something to take into account. Number 3 is a bit messy IMO.
It's been explicitly written in Django-facebook that they recommend it to be used with Userena:
We recommend using Django Userena. It seems easier to work with than
Django Registration. Both are supported and good packages though. To
use django userena simply point to the userena compatability layer.
AUTH_PROFILE_MODULE = 'django_facebook.FacebookProfile'
My question comes from AUTH_PROFILE_MODULE = 'django_facebook.FacebookProfile' part.
I have developed my own UserProfile in Userena and also inherited some system Actors like Student and Tutors based upon their own needs. so my setting is something like this:
AUTH_PROFILE_MODULE = 'profiles.UserProfile'
Within the django-facebook there is also an awesome example project that has written for mixing userena and django-facebook settings. But my problem still exists..
Should I replace my own code with facebook's one ??
This is my code :
from django.db import models
from django.contrib.auth.models import User
from django.utils.translation import ugettext as _
from django.db.models import permalink
from userena.models import UserenaBaseProfile
from courses.models import Course
class UserProfile(UserenaBaseProfile):
user = models.OneToOneField(User, unique=True, verbose_name=_('user'), related_name='user_profile')
department = models.ForeignKey('Department' , null = True , blank = True , related_name=_('user'))
name = models.CharField(max_length = 100)
birthday = models.DateField()
def __unicode__(self):
return self.name
class Student(UserProfile):
courses = models.ManyToManyField(Course , null = True, blank = True, related_name = _('student'))
Thanks beforehand ..
Just add the fields required for Facebook to your existing profile.
class UserProfile(UserenaBaseProfile, FacebookProfileModel):
....
(https://docs.djangoproject.com/en/dev/topics/db/models/#model-inheritance)
I have some model for a user profile and I want to be able to store several types of information but with some deferences (like a home or work phone number) but I don't want to use a ForeignKey relation ...how would I do that?
something like:
class Profile(models.Model):
phone = ? list of some kind ?
class Phone(???):
TYPE_CHOICES = (
('H', 'Home'),
('W', 'Work'),
('F', 'Fax'),
)
type = models.CharField(max_length = 1, choices = TYPE_CHOICES)
number = models.CharField(max_length = 16)
private = models.BooleanField()
Thank you!
Edit: I didn't want to use a foreign key just because I originally wanted all information relating to a user to show up on the profile admin page. but... meh... that's not too critical
Why don't you want to use a foreign key? If you want to have multiple phone numbers, that's the way you need/must do it.
It's easy to work with Django and foreign keys, and you can easily add an inline model formset into the admin page to create/edit a user profile with plenty of phone numbers.
Your models should look like this:
class Profile(models.Model):
name = models.CharField(max_length=40, verbose_name="User name")
class Phone(models.Model):
TYPE_CHOICES = (
('H', 'Home'),
('W', 'Work'),
('F', 'Fax'),
)
profile = models.ForeignKey(Profile)
type = models.CharField(max_length = 1, choices = TYPE_CHOICES)
number = models.CharField(max_length = 16)
private = models.BooleanField()
Then, you could use something like this to easily add/edit multiple phone numbers per profile in one admin page.
In your example, you should do something like this (inside admin.py file, in your django app):
class PhoneInline(admin.TabularInline):
model = Phone
class ProfileAdmin(admin.ModelAdmin):
inlines = [
PhoneInline,
]
admin.site.register(Profile, ProfileAdmin)
Now, you can go to the admin interface and try to add a new profile. You will see that you can add multiple phones per profile. I hope it helps you...
Finally, i'll recommend you to take a tour on django's tutorials. You will understand a lot of things and you will get a good idea of how to work with it.