Identify type of logged in user - django

I have a custom user setup like this:
class CustomUser(AbstractUser):
pass
class Employee(CustomUser):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
# other fields
In settings.py, I then add the following key:
AUTH_USER_MODEL = 'myapp.CustomUser'
I want to identify who logged in redirect them to appropriate views or urls.
In my account activation view, after the logging them in I redirect them to their appropriate page like this
if hasattr(user, 'employee'):
return redirect('edit_employee', slug=user.employee.slug)
else:
return redirect('index')
But this doesn't feel that right as I need to use this in other places like showing a different profile page link in the templates.
How do I better identify the regular user and employee in views and templates?

AFAIK you should not store different types of users in different tables. It will make your life pretty hard when defining relationships between other models and your users model.
My suggested approach would be having different profile models for different types of users and using a generic FK or some sort of other similar approaches to find out the user type and get their profile.
class CustomUser(AbstractUser):
USER_TYPE_EMPLOYEE = 'employee'
USER_TYPES = (
(USER_TYPE_EMPLOYEE, _('Employee')),
)
user_type = models.CharField(max_length=max(map(len, map(operator.itemgetter(0), CustomUser.USER_TYPES))), choices=CustomUser.USER_TPYES)
#property
def profile_model(self):
return {
USER_TYPE_EMPLOYEE: EmployeeProfile
}[self.user_type]
#property
def profile(self):
return self.profile_model.objects.get_or_create(user_id=self.pk)
class EmployeeProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='employee_profile')

The idea of extending the User Model to create an Employee Model doesn't seem good to me. Instead of this, you can use Django Group Model and add the user to the employee group. In this way, you can easily check if a user belongs to Employee group or not.
Additionally, you can also use django permissions here.
Assign your custom permissions to the employee group and restrict other users to view employee pages.

Related

Django 2.x - filter redirects based on relational database content

I have an app I am developing that has 2 types of user (A & B) and I have a page that I want to redirect users that are not authenticated and not type B. The user type is set in a relational database.
My two db tables for this are
User - pk, etc...
Profile - pk, uid (user pk), account_type
What I have done so far:
I have already used #login_required in my views.py doc to verify authentication successfully.
I can filter for the content where pid is set manually:
Profile.objects.filter(user__pk=1)
And I can get the current user id with request.user.id
What I am struggling with
What I have attempted to do is create a conditional, where I am using the request.user.id with the filter to find that user and then verify account type. I am not sure how to pull the account_type and at this point my code is becoming a mess, and probably ineffective. I've searched for this, but everything I have found using the keywords I have here has revealed different problems than what I am trying to solve.
Edited additional info
I have 2 apps at use here, 1 is accounts one is listings, the page I am doing this for is for posting a listing, that is in the listing app; I only want one type of user (B) to be able to post it.
in the accounts I have this class:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
location = models.CharField(max_length=30, blank=True)
account_type = models.CharField(max_length=30)
In the listing app I have not yet created a relevant model, my understanding was that this filtering would happen in the view.
In the view I am doing:
#login_required
def post(request):
current_user = request.user
if : #here is where I want to filter, but haven't been able to get it to work
else:
return redirect('index')
You will get the account type value directly as
account_type = request.user.profile.account_type
So, you could able to do the stuff according to the account_type value as below,
#login_required
def post(request):
account_type = request.user.profile.account_type
if account_type == "B"
# do your stuff
else:
return redirect('index')
Starting from your model design, if your system has the criteria that one user has only one profile. Then set uid in Profile model as OnetoOneField so that you can easily access account_type by User.objects.filter(id=request.user.id).profile.account_type.
Else, if your system has the criteria that one user can have multiple profile. Then you first need to access it profiles, and select the particular profile by adding more filter, then you access the account_type:
User.objects.filter(id=request.user.id).profile_set.all() gives you user's all profile.
User.objects.filter(id=request.user.id).profile_set.get(your_filter) gives you user's particular profile.
User.objects.filter(id=request.user.id).profile_set.get(your_filter).account_type gives you access to particular user's profile's account_type.
One to One relationship description here
Foreign (many to one) key description here

Django Custom Permission for Authorization

I am working on a Django Project, where one model (lets say Document) has the following field:
#In models.py
class Document (models.Model):
choice = (('Yes','Yes'), ('No','No'))
authorized = models.CharField (max_length=3, choices=choice, default='No')
Now, as a normal user creates a Document object, the authorized field is no. However, the superior needs to authorize the same before it is actually approved. Now, is there a permission system in django where one user can create an object but not authorize, whereas some other user - who has the permission - can authorize? If not, is the only way to do it is to create a custom field in user model and check it every time?
First of all, why you need to store possible values in CharField and not in BooleanField? I think you should consider changing to BooleanField.
You can do that by providing custom ModelAmin class in admin.py:
from django.contrib import admin
from .models import Document
#admin.register(Document)
class DocumentModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if not request.user.is_superuser:
self.exclude = ['authorized']
return super(DocumentModelAdmin, self).get_form(request, obj, **kwargs)
So now on admin page of Document if it is not a superuser, user won't see authorized field. You can change that code for checking if it should be specific user, or has some permissions, or if user belongs to some Group and etc.
UPDATE
If you want it to be in general views, you can just pass different forms to users, depend on their roles|permissions|groups(i don't know how your so called senior is different from rest of the users). So the answer would be: create two forms, then pass on of them in template based on your request.user attributes.
Django has awesome auth system. I couldn't understand you scenario.
But you could try something like this below
By default every Model object comes with three Permission object like (add_document, change_document and delete_document in your case).
If you want some custom permission you can add it in model Meta class like this:
You can add these permission to User object or Group object.
models.py
class Document (models.Model):
######
class Meta:
permissions = (("Can see document dashbaord", "see_document" ),)
and run python manage.py migrate to create new Permission object with codename as "see_document".
You can implement permissions in request handled by view like this:
view.py
from django.contrib.auth.mixins import PermissionRequiredMixin, permission_required
# For function based view
#pemission_required('document.see_document')
def someview(request):
######
pass
# For class based views
class SomeView(PermissionRequiredMixin, BaseView):
permission_required = 'document.see_document'
This could redirect any user with out the permssion to permission denied page. For more go through this https://docs.djangoproject.com/en/1.10/topics/auth/

Creating custom users in Django and customizing the admin

I need to create custom users in my app.
In the example given in the doc
class CustomUser(models.Model):
user = models.OneToOneField(User)
#custom fields
a user must exists before creating a CustomUser.
What I want to do is to create automatically a User when I create a CustomUser.
In the CustomUser admin (only visible by the superuser), I'd like to have only the custom fields and a few fields from the User model, as well as some form to allow the superuser to change the password for existing instance.
Anybody could help?
The first part of your question is easy, you can use a signal:
def create_custom_user(sender, instance, created, **kwargs):
if created:
custom_user, created = CustomUser.objects.get_or_create(user=instance)
post_save.connect(create_custom_user, sender=User)
As for the second part, theres already a change password form in the admin. To filter out the displayed fields you can create a CustomUserAdmin and register it together with the model. It's pretty self explaining in the django docs.
django docs: list_display

Django - Multiple User Profiles

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

Is it possible to limit of object creation of a model in admin panel?

I just want to know that is it possible to limit the number of objects of a model in admin panel?
It is that, for example, I have a model named 'Homepage' and in the admin panel I don't want a user can create more than one instance of Homepage.
Is there a way I can do this?
If it's just the admin that you want to affect (and don't want to affect the database model), you can create a custom ModelAdmin subclass:
class HomePageAdmin(admin.ModelAdmin):
def add_view(self, request):
if request.method == "POST":
# Assuming you want a single, global HomePage object
if HomePage.objects.count() > 1:
# redirect to a page saying
# you can't create more than one
return HttpResponseRedirect("foo")
return super(HomePageAdmin, self).add_view(request)
# ...
admin.site.register(HomePage, HomePageAdmin)
An alternative strategy to do the same thing is to create a custom ModelForm for HomePage, with a clean method that enforces the single HomePage requirement. This will make your requirement appear as a validation error, rather than as a redirect (or as a database error):
from django import forms
from django.forms.util import ErrorList
class HomePageModelForm(forms.ModelForm):
def clean(self):
if HomePage.objects.count() > 1:
self._errors.setdefault('__all__', ErrorList()).append("You can only create one HomePage object.")
return self.cleaned_data
# ...
class HomePageAdmin(admin.ModelAdmin):
form = HomePageModelForm
# ...
admin.site.register(HomePage, HomePageAdmin)
If it's "one HomePage per user", you will need HomePage to have a ForeignKey to User and adapt the above. You may also need to store the current User object in threadlocals in order to access it from HomePageModelForm.clean
If you want to limit Homepage to one for every user, then you could use one-to-one relation, with OneToOneField. As for limiting to N - a pre_save signal might be useful.
Try
class HomePage(models.Model):
user = models.ForeignKey(User, unique=True)
homepage = models.CharField(max_length=100, unique=True)
class Meta:
unique_together = (("user", "homepage"),)