I'm working on a Django project. There are two types of users. First type - Customer (UserProfile) is an extended built-in User because everybody has to have stored it's telephone, address etc. But there is another type of User called Translator. The translator has all permissions and attributes like Customer (UserProfile) and some new attributes - Languages,Prices etc.
I've chosen a probably most common way to handle a UserProfile:
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='userprofile')
telephone = models.CharField(max_length=40,null=True)
...
Now, I want to create Customer profile. I can't decide whether I should extend User (every user has already UserProfile) or to extend UserProfile itself.
So the option A is:
class TranslatorUserProfile(models.Model):
user_profile = models.OneToOneField(User)
languages ...
...
And option B is:
class TranslatorUserProfile(models.Model):
user_profile = models.OneToOneField(UserProfile)
languages ...
...
Translator will be able to access more pages than regular User which is a UserProfile and will be of course more attributes.
Is there some pattern? What should I do?
Logically, your translator extends the common user. In order to keep the db tables of both separate, you can make both subclass an abstract base model that defines their common attributes:
class AbstractUserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE,related_name='%(class)s')
telephone = models.CharField(max_length=40,null=True)
...
class Meta:
abstract = True
class UserProfile(AbstractUserProfile):
pass
class TranslatorUserProfile(AbstractUserProfile):
languages = ...
I have such a structure working fine in a project at work.
Related
In Django : From a Python OO perspective if I want different types of users shouldn't I have a "Teacher" Object Type AND "Student" Object" type extended from AbstractUser?
Looks like all the solutions mention to just extend with all the fields required for both users and only use the fields required for a Teacher or Student at Form time.
Just trying to stay with the OO method which makes sense to me from a newbie perspective.
Having different object types would allow me to test the object for the type instead of looking at an attribute and have only the needed fields for each type defined in the object.
Is there a correct way to do this so I have users of different object types?
Could I just embed an object of type Teacher or Student as a field in the User Model? Just a guess?
Let me know what you can and thanks for your expert polite response and patience.
Is this a good solution?
https://github.com/mishbahr/django-users2
This is the recurring theme I see on the internets...
django best approach for creating multiple type users
This is a bit like assigning a profile to a user. The way I'd do it would be a type field on a custom user model which then created a related model of either Teacher or Student depending on that type.
Looking at the age of the code in django-users2 there, I wouldn't use that with it being 6 or 7 years old. You might read through it & adapt it's concepts, but I wouldn't use it as it is.
The way I create profiles associated with a user is through a signal like this;
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save
from django.dispatch import receiver
from ..models import Profile
#receiver(post_save, sender=get_user_model())
def auto_create_profile(instance, **kwargs):
"""
Anytime a User object is created, create the associated Profile object
alongside it
"""
if kwargs.get('created', False):
Profile.objects.create(
user=instance
)
So you might adapt that to check the type as well;
if kwargs.get('created', False):
if instance.type == 'teacher':
Teacher.objects.create(
user=instance
)
Then your models just need a link to the user, which makes sense as a OneToOneField;
class Profile(models.Model):
"""
Profile model
"""
class Meta:
"""
Metadata
"""
app_label = 'accounts'
verbose_name = _('User profile')
verbose_name_plural = _('User profiles')
user = models.OneToOneField(
verbose_name=_('User'),
to=settings.AUTH_USER_MODEL,
related_name='profile',
on_delete=models.CASCADE
)
When I extend the Django User using a OneToOneField to create a Manager, the form to create a Manager in Django Admin only has fields for username and password.
class Manager(models.Model):
"""
Users who need to login to manage the listing of a Outlet
are represented by this model.
"""
manager = models.OneToOneField(User)
internal_notes = models.TextField(blank=True)
def __unicode__(self):
return self.manager.username
What is the right way to add the other built-in User fields like first_name etc to my Manager model?
It's not clear what you are asking here. The form to create a User (not a Manager) displays only three fields at first, to allow you to set the password, but then continues on to a second form where you can set other fields including first name.
Your Manager class doesn't need to define those fields, since they are attributes of the User class which you access via the one-to-one relation.
I have created my own custom user model in django and specified the same in settings.py :
AUTH_USER_MODEL = 'userprofile.User'
After this, I created User as an abstract class since I wanted to have two separate classes derived from this class namely - Vendor and Customer. This will create two separate database tables.
The problem occurs when I created another class - ProductReview. This class needs a foreign key to the user who added the review and this user can be either a Vendor or a Customer. I am not sure how to put this foreign key constraint here, because settings.AUTH_USER_MODEL will not work, as User is an abstract class.
Following is the class structure:
class UserManager(BaseUserManager):
class User(PermissionsMixin, AbstractBaseUser):
class Meta:
abstract = True
class Vendor(User):
class Customer(User):
class ProductReview(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='reviews', null=True, blank=True)
Edit - I went ahead with three different tables for each of the models.
For your case I would consider changing my modeling. This how I would do it:
Leave the default User as the auth user model for Django.
Create a Vendor and a Customer class with one-to-one relationship to the User model in order to extend it.
In this way, a you can have a ProductReview to reference a User, who could be either a Vendor or a Customer, or you can reference Customers only if you wish.
See more on Django documentation: Extending the User model.
The declaration of user field in ProductReview model class will map with the USER model. To define whether it is a Vendor or a Customer you need to declare another field probably integer field with mapping declared as
TYPE_OF_USER_MAPPING = {
1: 'Vendor',
2: 'Customer'
}
and then create a query to check in which model Vendor or Customer the original USER is.
Currently I have 4 classes in a model:
User(models.Model):
....
class Meta:
abstract=True
Sender(User):
....
Owner(User):
....
Report(models.Model):
sender = models.OneToOneField(Sender)
owner = models.OneToOneField(Owner)
The Sender/Owner extends the abstract base class User. I need a one to one relationship between a sender, a report, and an owner.
The problem is that I cannot create the Sender or Owner models because they have no differences in their fields (all the data they need is created in the abstract User model, and I created the subclasses for ease of representation). The solution I have come up with is this:
User(models.Model):
....
user_type = models.CharField(max_length=200)
class Meta:
abstract=True
Report(models.Model):
sender = models.OneToOne(User)
owner = models.OneToOne(User)
However I want to ensure that there is one sender and one owner per report. Is there a way to specify that the User MUST have a user_type of 'sender' or something along those lines? Or is there a better solution to this in general?
I am not a veteran user, so forgive me if I'm in the wrong here, but I don't know that it's good practice to define a new model called User when django.contrib.auth.models provides a base user model also named User. It seems to me like this would set you up for potential conflicts.
Since you want to have two different "types" of user, I would use the old approach of linking back from a related model.
from django.contrib.auth.models import User
from django.db import models
class Sender(models.Model):
user = models.OneToOneField(User, related_name='sender')
is_active = models.BooleanField(default=True)
class Owner(models.Model):
user = models.OneToOneField(User, related_name='owner')
is_active = models.BooleanField(default=True)
Say you create a new User with id=1, then create a new Sender called s keyed to that User. Remember, s is the Sender object, not the User object.
>>s.user
<user: username> #can vary with your own unicode definitions
>>s.user.id
1
>>print hasattr(s.user, 'sender')
True
>>print hasattr(s.user, 'owner')
False
>>print s.is_active
True
>>u = User.objects.get(id=1)
>>u.sender
<Sender: username> #again, what actually displays is controlled by your own 'def __unicode__'
>>print u.sender.is_active
True
>>print hasattr(u, 'sender')
True
>>print hasattr(u, 'owner')
False
Then for the report model:
class Report(models.Model):
sender = models.ForeignKey(Sender, related_name='report_sender')
owner = models.ForeignKey(Owner, related_name='report_owner')
Edit to answer your other question: how do you make sure a User has only a Sender or Owner model, but not both?
I don't know how to accomplish that on the model level. In practice, the easiest way I can think of to prevent this from being an issue would be through some custom form validation when a new Sender/Owner is created. If you're creating a new Sender, as part of validation make sure that the User you're tying it to doesn't already have an Owner model tied to it - and vice versa. Or create both the user and appropriate profile model at the same time.
You could alter your User registration form to have an extra field:
class UserRegistrationForm(UserCreationForm):
CHOICES = (
('s', 'Sender'),
('o', 'Owner'),
)
type = models.ChoiceField(max_length=2, choices=CHOICES)
class Meta: ...
Then when you're processing the form in your view:
...
if form.is_valid():
u = form.save()
type = form.cleaned_data['type']
if type == 's':
Sender.objects.create(user=u)
elif type == 'o':
Owner.objects.create(user=u)
(I apologize, but I am not somewhere I can test this. Please let me know if it doesn't work for you.)
I think the most straightforward way to resolve this is to override the save() method of the Report model and insert (before a super() call) a validation check to enforce your requirements.
I want the ability to let users indicate what countries they have visited.. my models.py looks something like this:
class User(models.Model):
name = models.CharField(max_length=50)
countries = models.ManyToManyField(Countries)
class Countries(models.Model):
#This is where I don't know what to do.
#The end goal is for the user to be able to check off what countries he/she has visited
You would create the relationship the other way around
class User(models.Model):
name = models.CharField(max_length=50)
class Countries(models.Model):
user = models.ForeignKey(User)
If you are using django's built in User stuff then you only need this.
from django.contrib.auth.models import User
class Countries(models.Model):
user = models.ForeignKey(User)
Relation fields already generate an attribute on the other model for the reverse relation unless explicitly disabled.
You're fine as is w/r/t the ManyToManyField.
Now you'll want to create a form for this model to allow the checking-off to be done by your users.