I am trying to implement an authentication system for 2 different sets of users in Django. A student(using his username & password), A developer (using his email & password). Currently I have a (common) UserProfile model which will be shared by Student, Developer. Here is my models code:
class UserProfile(models.Model):
user = models.OneToOneField(User)
name = models.CharField(max_length=50, null=True, blank=True)
slug = models.SlugField(max_length=50, db_index=True, unique=True)#this will be used as his unique url identifier
objects = UserProfileManager()
class Meta:
abstract = True
class Student(UserProfile):
''' some student specific fields might go here '''
class Developer(UserProfile):
''' some developer specific fields might go here '''
And in settings.py I gave:
AUTH_PROFILE_MODULE = 'users.UserProfile'
I was getting authentication working with just UserProfile model. But as soon as I introduced Student, Developer the whole thing screwed up. I am getting
UserProfile has no attribute
'DoesNotExist'
(this is from the UserProfileManager exists method) and also
SiteProfileNotAvailable
error. (I am getting these errors even before I started writing email auth backend.). Am I missing anything ? What's the best path to follow to achieve what I wanted.
The problem might be the abstract definition of UserProfile. Things should work when you leave the 'abstract = True' away. But be aware of how this changes your table scheme.
Related
Maybe this question also is similar to something like "automatic execution of raw SQL code just before creating exact one special model in models.py with managed=False".
For example, I have 3 models in models.py (default User, UserTypes and relation between them):
from django.contrib.auth.models import User
class UserTypes(models.Model):
type = models.TextField(unique=True)
class Meta:
db_table = 'user_types'
class UsersHaveTypes(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
type = models.ForeignKey(UserTypes, on_delete=models.CASCADE)
class Meta:
db_table = 'users_have_types'
unique_together = (("user", "type"), )
I have few types of users one of which is clients. Now I want to create endpoints "/clients", which will work as a usual model (CRUD).
Right now, I just add this in models.py:
class Clients(models.Model):
first_name = models.TextField(blank=True, null=True)
last_name = models.TextField(blank=True, null=True)
# all another fields from User model, which duplicated in the database view
class Meta:
managed = False
db_table = 'clients'
and then I make this by hand in my database backend:
Create database view
Create a function to make Insert, Update
and Delete
Create a trigger to trigger this functions.
It's a bunch of code and there is nothing special, all worked fine.
This all is to make working CRUD on "/clients" endpoint, so, for example, on "Creation", it will create User and automatically add a correct row to users_have_types table, which marks this user as "client".
Is there some more elegant and automatical way to make this? I move my Django project, and I need to create view, function, and trigger in the database backend again, which takes a lot of time (all by hand for every "type of users") and is an ugly decision.
I'm surprised, that nobody asked this question before, cause it's a good style of code database to make some views, and hide real table behind them. I know that Django can't create database view by self, but there must be a way to describe custom SQL code, which is used to create a table in the database. Maybe something with managers, I don't know (I'm a newbie in Django). Of course, it will be specific only for one database backend, but it's fine.
In Django, I have installed allauth. Then I have created a new app, where user's actions will be. I want to link each of the actions with allauth's user data within EmailAddress model.
My questions are:
Normally, data is defined by user_action = models.CharField(max_length=200) and such. ForeignKey on user action does not allow defining field types, at least from what I've seen. How can I define it, or is it okay not to define it?
How can I define the relationship with data in allauth's model that's not anywhere near this new app? For example, I have:
from django.db import models
import allauth.account.models
class Button(models.Model):
button_one = models.ForeignKey('EmailAddress', on_delete=models.CASCADE)
def __str__(self):
return self.button_one
It does not work. The error shows:
input.Button.comment: (fields.E300) Field defines a relation with model 'EmailAddress', which is either not installed, or is abstract.
input.Button.comment: (fields.E307) The field input.Button.comment was declared with a lazy reference to 'input.emailaddress', but app 'input' doesn't provide model 'emailaddress'.
The allauth model data ("user") in question is:
class EmailAddress(models.Model):
user = models.ForeignKey(allauth_app_settings.USER_MODEL,
verbose_name=_('user'),
on_delete=models.CASCADE)
email = models.EmailField(unique=app_settings.UNIQUE_EMAIL,
max_length=app_settings.EMAIL_MAX_LENGTH,
verbose_name=_('e-mail address'))
verified = models.BooleanField(verbose_name=_('verified'), default=False)
primary = models.BooleanField(verbose_name=_('primary'), default=False)
objects = EmailAddressManager()
I'm using virtualenv and have allauth installed within the project.
So EmailAddress is your User model?
In that case you might be able to do this via the standard way by referring to settings.py
Import . from settings
class Button(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
In Django model I am making a table 'followers', which has:
user's id. (this is followed by)
user's id (this is follower)
that's simple a user can follow other users.
How should I define the model in Django?
I tried this, but does not work:
user = models.ForeignKey('self')
follower_id = models.ForeignKey('self')
How should this be done?
thanks
The 'self' argument won't work unless you have a model called self.
Assuming that your assignment model is called Following, and you're using the built in User model then you can do:
class Following(models.Model):
target = models.ForeignKey('User', related_name='followers')
follower = models.ForeignKey('User', related_name='targets')
This will likely need some further uniqueness and validation logic.
Note the related_name attribute, see https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.ForeignKey.related_name. This means that for a given user object you can do user.targets.all() to get users they follow, and user.followers.all() to get users who follow them.
Note also that Django returns target model instances, not IDs, in the ORM. This means that even though the underlying table may be called follower_id, in the python code following.follower will return an actual User object.
Seeing as Following is actually the through table for the many-to-many relationship between Users. I would create a Profile model which extends the Django User model, and then declare the many-to-many relationship (using ManyToManyField).
from django.contrib.auth.models import User
from django.db import models
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
following = models.ManyToManyField(User, related_name='followers')
Use the many to many field.
followers = models.ManyToManyField('self', symmetrical=False)
Let's say I have three models in my app that lets user write a review for a book. The requirement is that every user can only do one review for each book.
Review model has two foreign keys to User and Book models. I want to be able to validate user input before creating an instance of Review model to ensure a user can not create more than one review per book.
I know how to do it if I ask the user to provide the User and Book information in the data that's sent to DRF version 3.+.
But the url to post a new Review has the Book id in it, and the user is authenticated:
url used to list and create reviews: /book/{book_id}/reviews/
Right now when I do a POST operation on the url, DRF complains that the book field is required, even though I tried to send keyword arguments to the overloaded perform_create function (see snippets below)
My guess is I should send the information about book and review that's embedded in the url and request to the default UniqueTogetherValidator but I don't know how to do it!
I have seen a partial solution in this question, but my problem is that I not only have to provide the current authenticated user to the validator but also the book information, since I am requiring a unique_together constraint.
I have unsuccessfully tried providing default kwarg to the fields.
Also tried providing a custom validator in the serializer, which didn't succeed.
So my question is how to validate unique_together of input data from user's request when the book information is embedded in the url and the user information is part of the incoming request (while both of those are related foreign keys and not local attributes of Review model)?
Here are my models and serializers:
models.py
class Review(models.Model):
# Relationships
book = models.ForeignKey(Book)
user = models.ForeignKey(User)
comment = models.TextField(null=True, blank=True)
class Meta:
unique_together = (("user", "book"),)
serializers.py
class ReviewSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(read_only=True)
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Review
Part of views.py
class ReviewList(generics.ListCreateAPIView):
serializer_class = ReviewSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user, book=self.kwargs['book_id'])
urls.py
url(r'^book/(?P<book_id>\d+)/reviews/$', view=ReviewList.as_view(), name='review-list')
You are trying to make things harder than they should.
My feeling on this issue is that the unique together constraint is part of the business rules. Therefore I'd go with removing that constraint on the serializer. After the serializer has validated the data you'll be able to check the constraint on the review (owner + book) and raise a validation error if there's already one. Doing this in the perform_create before saving the serializer seems sensible.
To remove the unique together constraint you'll have to explicitly set the validators on the serializer:
class ReviewSerializer(serializers.ModelSerializer):
book = serializers.PrimaryKeyRelatedField(read_only=True)
user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Review
validators = []
Make sure to print a serializer instance before doing that change so you can make sure you're not removing other contraints.
Initially, I started my UserProfile like this:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.OneToOneField(User)
verified = models.BooleanField()
mobile = models.CharField(max_length=32)
def __unicode__(self):
return self.user.email
Which works nicely along with AUTH_PROFILE_MODULE = 'accounts.UserProfile' set in settings.py.
However, I have two different kinds of users in my website, Individuals and Corporate, each having their own unique attributes. For instance, I would want my Individual users to have a single user only, hence having user = models.OneToOneField(User), and for Corporate I would want them to have multiple users related to the same profile, so I would have user = models.ForeignKey(User) instead.
So I thought about segregating the model into two different models, IndivProfile and CorpProfile, both inheriting from UserProfile while moving the model-specific attributes into the relevant sub-models. Seems like a good idea to me and would probably work, however I would not be able to specify AUTH_PROFILE_MODULE this way since I'm having two user profiles that would be different for different users.
I also thought about doing it the other way around, having UserProfile inherit from multiple classes (models), something like this:
class UserProfile(IndivProfile, CorpProfile):
# some field
def __unicode__(self):
return self.user.email
This way I would set AUTH_PROFILE_MODULE = 'accounts.UserProfile' and solve its problem. But that doesn't look like it's going to work, since inheritance in python works from left to right and all the variables in IndivProfile will be dominant.
Sure I can always have one single model with IndivProfile and CorpProfile variables all mixed in together and then I would use the required ones where necessary. But that is just doesn't look clean to me, I would rather have them segregated and use the appropriate model in the appropriate place.
Any suggestions of a clean way of doing this?
You can do this in following way. Have a profile which will contains common fields which are necessary in both profiles. And you have already done this by creating class UserProfile.
class UserProfile(models.Model):
user = models.ForeignKey(User)
# Some common fields here, which are shared among both corporate and individual profiles
class CorporateUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Corporate fields here
class Meta:
db_table = 'corporate_user'
class IndividualUser(models.Model):
profile = models.ForeignKey(UserProfile)
# Individual user fields here
class Meta:
db_table = 'individual_user'
There is no rocket science involved here. Just have a keyword which will distinguish between corporate profile or individual profile. E.g. Consider that the user is signing up. Then have a field on form which will differentiate whether the user is signing up for corporate or not. And Use that keyword(request parameter) to save the user in respective model.
Then later on when ever you want to check that the profile of user is corporate or individual you can check it by writing a small function.
def is_corporate_profile(profile):
try:
profile.corporate_user
return True
except CorporateUser.DoesNotExist:
return False
# If there is no corporate profile is associated with main profile then it will raise `DoesNotExist` exception and it means its individual profile
# You can use this function as a template function also to use in template
{% if profile|is_corporate_profile %}
Hope this will lead you some where. Thanks!
I have done it this way.
PROFILE_TYPES = (
(u'INDV', 'Individual'),
(u'CORP', 'Corporate'),
)
# used just to define the relation between User and Profile
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey('Profile')
type = models.CharField(choices=PROFILE_TYPES, max_length=16)
# common fields reside here
class Profile(models.Model):
verified = models.BooleanField(default=False)
I ended up using an intermediate table to reflect the relation between two abstract models, User which is already defined in Django, and my Profile model. In case of having attributes that are not common, I will create a new model and relate it to Profile.
Could be worth to try using a through field. The idea behind it is to use the UserProfile model as through model for the CorpProfile or IndivProfile models. That way it is being created as soon as a Corp or Indiv Profile is linked to a user:
from django.db import models
from django.contrib.auth.models import User
class UserProfile(models.Model):
user = models.ForeignKey(User)
profile = models.ForeignKey(Profile, related_name='special_profile')
class Profile(models.Model):
common_property=something
class CorpProfile(Profile):
user=models.ForeignKey(User, through=UserProfile)
corp_property1=someproperty1
corp_property2=someproperty2
class IndivProfile(Profile):
user=models.ForeignKey(User, through=UserProfile, unique=true)
indiv_property1=something
indiv_property2=something
I think that way it should be possible to set AUTH_PROFILE_MODULE = 'accounts.UserProfile', and every time you create either a CorpProfile or a IndivProfile that is linked to a real user a unique UserProfile model is created. You can then access that with db queries or whatever you want.
I haven't tested this, so no guarantees. It may be a little bit hacky, but on the other side i find the idea quite appealing. :)