I'm trying to figure out how to create custom groups in Django, in way that groups are related to application and not to a Project.
For example when a user want to create a company, it should be the owner of the company:
model
class Company(models.Model):
owner = models.ForeignKey(User)
name = models.CharField(max_length=64, unique=True)
description = models.TextField(max_length=512)
created_on = models.DateTimeField(auto_now_add=timezone.now)
class Meta:
permissions = (
("erp_view_company", "Can see the company information"),
("erp_edit_company", "Can edit the company information"),
("erp_delete_company", "Can delete the company"),
)
view
# Create your views here.
def register(request, template_name='erp/register.html'):
if request.method == 'POST':
company_form = CompanyRegistrationForm(request.POST)
if company_form.is_valid():
# Create a new company object but avoid saving it yet
new_company = company_form.save(commit=False)
# Set the owner
if request.user.is_authenticated():
new_company.owner = request.user
# Add the user to group admin
group = Group.objects.get_or_create(name="erp_admin")
request.user.groups.add(group)
# Save the User object
new_company.save()
template_name = 'erp/register_done.html'
return render(request, template_name, {'new_company': new_company})
else:
company_form = CompanyRegistrationForm()
return render(request, template_name, {'company_form': company_form})
Here the questions:
1) Instead of having Group.objects.get_or_create(name="erp_admin") I'd like to have CompanyGroup.objects.get_or_create(name="admin") in way that the Groups of an application are restricted to the application.
2) How I can map the permissions defined in the Meta class of each model to a group?
3) The custom groups are related to a Company, this mean each company has the group Admin, that starts with the owner. The owner can creates user like "Manager". The manager can creates groups/permissions, add user, set permission to user but can't in anyway CRUD the admin groups. So there's a kind of hierarchy in groups (I suppose that the hierarchy depend on the permissions that are added to a group).
To make it more clear here you can see a picture of the concept:
So I think the main problems are:
1) How to inherit Groups to create custom groups.
2) How to map the permissions of a model to a group.
3) How groups can be restricted to a CompanyID.
I hope the problem is well defined. Let me know if I have to clarify something.
Thanks in advance for the support.
Edit 1)
For the point 3 I found this answer on SO: Extend django Groups and Permissions but the problem then is how to query for those data and how to check the permissions. And how could you override the save() method to add meta information to the property 'name'? The group name could be something like: "company_id#admin" or "company_id#manager".
You may be looking for Per object permissions. Django does not support this out of the box but it is possible with the app Django-guardian
Related
I am building a BlogApp AND i made a feature of Favorite Users in the ManyToManyField.
It means user_1 can add multiple favorite users in one field. I build a view to add users.
BUT when i create an instance to store favorite users from Admin before adding favorite users from site AND then if i add favorite users from site then they are adding. BUT if there is not already a instance of request.user's FavouriteUsers in Admin then it is not adding and creating a new object.
So, A new object for storing favorite users of request.user is not adding, AND if object is already there then they are adding.
models.py
class FavoriteUsers(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
favorite_users = models.ManyToManyField(User, related_name='favorite_users', blank=True)
views.py
def AddFavUsers(request,user_id):
obj = FavoriteUsers.objects.filter(user=request.user)
user = get_object_or_404(User,id=user_id)
for ob in obj:
ob.favorite_users.add(user)
ob.user.add(request.user)
ob.save()
return redirect('home')
Image of already created object
When object is already , manually created then favorite users are adding BUT if it is not then new object is not creating.
Any help would be much Appreciated.
Thank You in Advance.
Your view is basically adding the user to all FavoriteUsers that are linked to the User. But it is not said at all that there are such FavoriteUsers.
We thus can construct a FavroiteObject with:
def AddFavUsers(request,user_id):
obj = FavoriteUsers.objects.create(
user=request.user
)
obj.favorite_users.add(user_id)
# …
the modeling is also a bit odd, since we here create a third table, it makes more sense to construct a model Favorite with two ForeignKeys that thus acts as a junction table. So this should look like:
from django.conf import settings
class Favorite(models.Model):
user_to = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='favorites_from'
)
user_from = models.ForeignKey(
settings.AUTH_USER_MODEL,
related_name='favorites_to'
)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user_from', 'user_to'], name='favorite_only_once')
]
If you then want to make a user_id a favorite of the logged in user, you can work with:
from django.contrib.auth.decorators import login_required
#login_required
def AddFavUsers(request, user_id):
Favorite.objects.create(user_from=request.user, user_to_id=user_id)
# …
You can then obtain the favorite users of a user with:
User.objects.filter(favorites_from__user_from=my_user)
where my_user is the user from which you want to obtain the favorite users.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
The ManyToMany field already handles this. You can add a favorite_users property to your User model, which will point to the User model and Django will create the connecting table. Beware of the symmetrical parameter, if you leave it out it'll be a bidirectional relation.
class MyUser(models.Model):
...
favorite_users = models.ManyToManyField("MyUser", symmetrical=False, blank=True)
If you still want to write the connecting model yourself, check out the through parameter.
Let's say I have 2 models as follows. As there is a many to many relation between the models, django created the required table (clientreport) and the table's permissions can be set using django admin's group permission's tab. (see screenshot)
class Report(models.Model):
is_visible = models.BooleanField(default=False)
clients = models.ManyToManyField(Client)
class Client(models.Model):
name = models.CharField(max_length=32)
On django admin I granted change permissions to a specific user group (e.g. group_a) on Report model. I did NOT grant any permissions on clientreport model.
My desired output is, a group_a user can change is_visible field of any Report instance but could not change/delete client X reports (or clientreport).
However even if there is no actual table reference between Report and Client models, a group_a user can still edit client-reports from django admin panel. (see screenshot)
Is this really intended? If so, how can I get my desired goal?
If you need to restrict access to certain fields by certain rights, you can use the ModelAdmin.get_readonly_fields method.
Usage example:
class ReportAdmin(admin.ModelAdmin):
...
def get_readonly_fields(self, request, obj = None):
if request.user.groups.filter(name=groupname).exists():
return ('field1', 'field2')
else:
return super().get_readonly_fields(request, obj)
I have my user table in django, and to differ all the users I created two tables, (Teacher and Student).
Both tables are getting an fk from user
So, in order to make authorization how do I check if one's user is in a certain table.
I need to check it this way
def test_func(self):
return self.request.user.check..if..it..exists..in..table
My models are like this.
class Teacher(models.Model):
User = models.OneToOneField(settings.AUTH_USER_MODEL)
This depends on how your models are set up.
If your Teacher model looks something like this;
class Teacher(models.Model):
user = models.ForeignKey(User)
Then you should be able to check if the user is a teacher by using the implicit backref;
self.request.user.teacher_set.exists()
As the question has been updated to show that the model is slightly different than I anticipated, here is an update.
class Teacher(models.Model):
user = models.OneToOneField(User)
Which means that the backref will be a little different.
hasattr(self.request.user, "teacher")
As you've mentioned that you are doing this inside a django template, I'm pretty sure that the following will work:
{% if user.teacher %}
Since you haven't posted your models, I am giving you a rough idea how to do it.
in your views.py -
from .models import Teacher,Student
def test_func(request):
user = request.user
if (Teacher.objects.filter(user=user).count() > 0) or (Student.objects.filter(user=user).count > 0):
#do your stuffs here..
One way is to query both tables:
teacher = Teacher.objects.filter(user=self.request.user)
student = Student.objects.filter(user=self.request.user)
if teacher or student:
# do what you want.
If you put in your relation the argument "related_name" you can do it using inverse relationship
class SomeTable(models.Model):
user = models.ForeignKey(
User, #Your user model or Django one
verbose_name = "User",
related_name = "inverse_relation_name"
)
Then you have to call using keyword arguments for the filters:
SomeTable.inverse_relation_name.filter(id=self.request.user.id) #You will get a queryset
Or
SomeTable.inverse_relation_name.get(id=self.request.user.id) # You will get the object or a exception
I am building an API that should have the following kind of users
super_user - create/manage admins
admin - manage events(model) and event participants
participants - participate in events, invited to events by admins
Additional i want to have each type of user to have phone number field
I tried
class SuperUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
class Admin(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
class Participant(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
phone_number = models.CharField(max_length=20)
But gut is telling me its a wrong way to handle this. Can someone please help.
One possible solution is:
Have only one User Model with role field, which defines what user role is.
Create a User Group and add each group needed permissions.
Add User to User Group
Limit access using a Django REST Framework (later DRF) Permission Class.
Explanation:
Using only one user model is a more simple and flexible solution. You can query all users, or filtered by feature (like user role). Standart Django auth system expects one UserModel.
Read more about Django user groups. See "Django Permissions Docs #1" and "Django Groups Docs #2". Also useful is "User groups and permissions".
You need to create a group for each user role, and add needed permissions for each group. (Django has a default model permission, created automatically, look at the docs on the given links) or create the needed permission manually in the model definition.
Manually or using a script, add User to the needed group by defining his role when a user is created or manually by Django Admin interface.
Now everything should be ready for limited access by the user's role. You can easily limit access to the DRF View using a permission class. See more information in the "DRF Permission Docs".
Let's define our own:
from rest_framework.permissions import DjangoModelPermissions
# Using DjangoModelPermissions we can limit access by checking user permissions.
# Rights need only for CreateUpdateDelete actions.
class CUDModelPermissions(DjangoModelPermissions):
perms_map = {
'GET': [],
'OPTIONS': [],
'HEAD': ['%(app_label)s.read_%(model_name)s'],
'POST': ['%(app_label)s.add_%(model_name)s'],
'PUT': ['%(app_label)s.change_%(model_name)s'],
'PATCH': ['%(app_label)s.change_%(model_name)s'],
'DELETE': ['%(app_label)s.delete_%(model_name)s'],
}
# Or you can inherit from BasePermission class and define your own rule for access
from rest_framework.permissions import BasePermission
class AdminsPermissions(BasePermission):
allowed_user_roles = (User.SUPERVISOR, User.ADMINISTRATOR)
def has_permission(self, request, view):
is_allowed_user = request.user.role in self.allowed_user_roles
return is_allowed_user
# ----
# on views.py
from rest_framework import generics
from .mypermissions import CUDModelPermissions, AdminsPermissions
class MyViewWithPermissions(generics.RetrieveUpdateDestroyAPIView):
permission_classes = [CUDModelPermissions, ]
queryset = SomeModel.objects.all()
serializer_class = MyModelSerializer
You can add additional permission class to combine access limitation.
So in Django any user has a flag is_superuser that corresponds to your 'superuser'. So just use that - e.g. User.objects.create(is_superuser=True).
For the rest you can simply use a field for a normal User model to differentiate between subroles of a normal user.
class User(AbstractBaseUser):
can_participate_event = models.Boolean(default=False)
can_create_event = models.Boolean(default=False)
Or
class User(AbstractBaseUser):
permissions = models.CharField(default='') # and populate with e.g. 'create_event,participate_event'
Still you will need to check all those fields in your view probably. The more you add to your application, the hairier this becomes so I would suggest using a 3rd party library like rest-framework-roles (I'm the author) or guardian.
I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.