django access control based on a model field value - python

I have a model class Department with a field name. I have another Model Student with a foreign key to Department. I want to control access to Student objects based on department. That is, a user with permission to edit the department with name "CS" can only edit that fields. How this can be achieved in Django? (I'm using django 1.8, python3)
Edit
class Department(models.Model):
name = models.CharField(_('department name'), max_length=255)
class Students(models.Model):
first_name = models.CharField(_('first name'), max_length=30)
last_name = models.CharField(_('last name'), max_length=30)
department = models.ForeignKey('Department')
Also I'm creating required permissions dynamically while adding new department.(eg: if department.name for new entry is 'CS', 2 permissions like 'view_CS' and 'edit_CS' will be created)

Based on http://django-guardian.readthedocs.org/en/v1.2/userguide/assign.html#for-group
class Department(models.Model):
name = models.CharField(_('department name'), max_length=255)
class Meta:
permissions = (
('view', 'View department'),
('edit', 'Edit department'),
)
Somewhere in views:
from django.contrib.auth.models import Group
cs_department = Department.objects.get(name='cs_department')
cs_department_group = Group.objects.create(name=cs_department.name)
assign_perm('view', cs_department_group, cs_department)
assign_perm('edit', cs_department_group, cs_department)
request.user.groups.add(cs_department_group)
print(request.user.has_perm('view', cs_department)) # True
print(request.user.has_perm('edit', cs_department)) # True

Since my application is pretty big, I cannot afford changing entire data references to accomodate the permissions as #madaohan's answer.
This kind of access control mechanisms can be easily used defining a custom model manager(docs) and a middleware to get logged in user object in models(Check this link),

Related

Django How to organize models so there is only one owner

I have two models Company and User:
class Company(models.Model):
name = CharField("Name", max_length=60)
owner = OneToOneField(
"Employee", related_name='owner', on_delete=SET_NULL, null=True)
class BaseUser(AbstractBaseUser, PermissionsMixin):
objects = CustomUserManager()
join_date = DateTimeField(default=timezone.now)
name = CharField("Name", max_length=60)
email = EmailField(('Email'), unique=True)
company = ForeignKey(Company, on_delete=models.CASCADE)
I need to have only one User as owner. I made it so two models point to each other which doesn't seem right. Is there a way to fix it and still have only one possible owner? I know standard way would be to add is_owner = Boolean to User but it allows other Users to be owners.
A ForeignKey from the Company to the BaseUser is sufficient:
class Company(models.Model):
name = CharField("Name", max_length=60)
owner = ForeignKey(
'Employee',
related_name='companies'
)
class BaseUser(AbstractBaseUser, PermissionsMixin):
objects = CustomUserManager()
join_date = DateTimeField(default=timezone.now)
name = CharField("Name", max_length=60)
email = EmailField(('Email'), unique=True)
# no company
Here we thus link a Company to one user. Two Companys can have the same owner, and it is possible that the BaseUser has no, one, or more Companys where he is the owner.
You can obtain all the companies for which a BaseUser is the owner with:
myuser.companies.all()
If two companies should always point to two different users, you can make use of the OneToOneField [Django-doc].

Field 'id' expected a number - Uploading ForeignKey to django-import-export

I am trying to import data from an csv file into a django db using django-import-export. My problem is trying to upload data with a ForeignKey as an object. I have migrated, followed docs, and still no solution. You can see my error below in the django admin:
Here is my csv data with a blank 'Id' column:
models.py
from django.db import models
from django.shortcuts import reverse
from urllib.parse import urlparse
class States(models.Model):
name = models.CharField(max_length=96, blank=False, unique=True)
abbrv = models.CharField(max_length=2, null=True, blank=True)
class Meta:
ordering = ['name']
verbose_name = 'State'
verbose_name_plural = 'States'
def __str__(self):
return f'{self.name}'
class Person(models.Model):
last_name = models.CharField(
max_length=255, help_text="Enter your last name.")
first_name = models.CharField(
max_length=255, help_text="Enter your first name or first initial.")
address = models.CharField(
max_length=255, blank=True, help_text="Enter your street address.")
city = models.CharField(
max_length=255, blank=True, help_text="Enter your city.")
state = models.ForeignKey('States', to_field='name', on_delete=models.SET_NULL, null=True)
zipcode = models.CharField(max_length=50)
website = models.URLField(
max_length=255, blank=True)
profession = models.CharField(max_length=50, blank=True)
# META CLASS
class Meta:
verbose_name = 'Person'
verbose_name_plural = 'Persons'
ordering = ['last_name', 'first_name']
# TO STRING METHOD
def __str__(self):
"""String for representing the Model object."""
return f'{self.last_name}, {self.first_name}'
admin.py:
from django.contrib import admin
from .models import Person, States
from import_export.admin import ImportExportModelAdmin
from import_export.widgets import ForeignKeyWidget
from import_export import fields, resources
class PersonResource(resources.ModelResource):
state = fields.Field(
column_name='state',
attribute='state',
widget=ForeignKeyWidget(States, 'name'))
class Meta:
model = Person
class PersonAdmin(ImportExportModelAdmin):
list_display = ('last_name', 'first_name', 'state')
search_fields = ('first_name', 'last_name' )
resources_class = PersonResource
admin.site.register(Person, PersonAdmin)
admin.site.register(States)
I think you need to specify both in your question here, as well as to Django how you want the id field treated.
Do you want it propagated with the Django id or pk (sometimes the same sometimes not)? Then you would have id=self.id or id=self.pk somewhere in your view for the datatable.
Do you want your database to create a unique key?
You would need to add some functionality someplace to tell Django how to fill in that field.
Also, if you want it to create an id different from the Django id or pk then you would need to add the field to your model.
https://docs.djangoproject.com/en/3.1/ref/forms/validation/
https://docs.djangoproject.com/en/3.1/ref/validators/
https://docs.djangoproject.com/en/3.1/ref/forms/api/
Or, perhaps after Validation of the form, when you create the object. Add something to the effect of id=[database function to create unique id].
Another solution might be a templateTag or templateFilter to create a value on the form side if you want to create the id based on info contained in the form. Like combining last 4 of name with time of submission.
https://docs.djangoproject.com/en/3.1/ref/templates/builtins/
https://docs.djangoproject.com/en/3.1/howto/custom-template-tags/
Having just re-read your question, also, I'm not sure but you might be asking if the database can support an embedded reference to another object. Is ID a reference to another model's key? That's a whole different question. And it is database specific.
Last Suggestion: Perhaps a re-read of:
https://docs.djangoproject.com/en/3.1/ref/forms/fields/#fields-which-handle-relationships
This error is occur because your id did not received an id or int value it received a str type of value Wyoming try to pass int value in id
Update
just update your PersonResource Meta class like this
class PersonResource(resources.ModelResource):
state = fields.Field(
column_name='state',
attribute='state',
widget=ForeignKeyWidget(States, 'name'))
class Meta:
model = Person
import_id_fields = ['id']
The default field for object identification is id, you can optionally
set which fields are used as the id when importing
check official doc. for more information.

Having trouble wrapping my head around follower/target models in Models.py

I have just started with making a similar site to Pinterest and the site has follower/target system that I have barely any understanding of. So far, my models.py code is below:
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, null=True)
email = models.CharField(max_length=200, null=True)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45, null=True)
target = models.ManyToManyField(self, through='Follow')
follower = models.ManyToManyField(self, through='Follow')
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='targets')
target = models.ForeignKey(User, on_delete=models.CASCADE, related_name='followers')
class Meta:
db_table = 'follows'
This code was made with reference to another StackOverflow thread
Django models: database design for user and follower
However, I am having trouble understanding how using "related_name='targets' in 'follower' and "related_name='followers'" in 'target' where I can't see any 'targets'(plural) or 'followers'(plural) in other areas of models.py
Should I get rid of that related_name, since there is no such table called "followers" or "targets"? And if you spot major errors in my code or logic, can you tell me? Thanks!
Should I get rid of that related_name, since there is no such table called followers or targets.
There is never a table named followers or targets. The related_name [Django-doc] is a conceptual relation Django makes to the other model (in this case User). It means that for a User object myuser, you can access the Follow objects that refer to that user through target for example with myuser.followers.all(), so:
Follow.objects.filter(target=myuser)
is equivalent to:
myuser.followers.all()
The default of a related_name is modelname_set, so here that would be follow_set. But if you remove both related_names, then that would result in a name conflict, since one can not add two relations follow_set to the User model (and each having a different semantical value).
if you spot major errors in my code or logic, can you tell me?
The problem is that since ManyToManyFields refer to 'self' (it should be 'self' as string literal), it is ambigous what the "source" and what the target will be, furthermore Django will assume that the relation is symmetrical [Django-doc], which is not the case. You should specify what the source and target foreign keys are, you can do that with the through_fields=… parameter [Django-doc]. It furthermore is better to simply define the related_name of the ManyToManyField in reverse, to avoid duplicated logic.
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, unique=True)
email = models.CharField(max_length=200)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45)
follows = models.ManyToManyField(
'self',
through='Follow',
symmetrical=False,
related_name='followed_by',
through_fields=('follower', 'target')
)
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='targets'
)
target = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='followers'
)
class Meta:
db_table = 'follows'
Here a User object myuser can thus access myuser.follows.all() to access all the users that they follow, myuser.followed_by.all() is the set of Users that follow myuser. myuser.targets.all() is the set of Follow objects that he is following, and myuser.followers.all() is the set of Follow objects that are following that user.

How to access data using reverse foreign key reference in django

I have a model named UserProfile and a model PersonalInformation. I would like to fetch all the data of PersonalInformation using UserProfile model when the user is logged into the webiste but i have a foreign key refernce in the PersonalInformation model with the UserProfile model so how do i fetch the personal information using UserProfile model?
User Profile Model :
class UserProfile(models.Model):
"""Represents a user's model inside our system"""
email = models.EmailField(max_length=255, unique=True)
name = models.CharField(max_length=255)
profile_picture = models.ImageField(upload_to='photos/%y/%m/%d/')
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
highest_degree_earned = models.CharField(max_length=255, blank=False)
college_name = models.CharField(max_length=255, blank=False)
graduation_year = models.IntegerField(default=2020, blank=False)
Personal Information Model :
class PersonalInformation(models.Model):
"""Represents a user's personal Infromation inside our system"""
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
mobile = models.CharField(max_length=10 ,blank=True)
bio = models.TextField(max_length=200, blank=True)
college_university = models.CharField(max_length=100, blank=False)
course = models.CharField(max_length=100, blank=False)
First of all, in the code, you are showing you have the names of the models wrong. The UserProfile model name is set as PersonalInformation, change it or the migrations won't work (it's not accepted on the database no matter which one you're using).
Referent to the question you're asking, to fetch the related instance of PersonalInformation of a certain UserProfile instance you should just query the next:
user = UserProfile.objects.get(id='') #Introduce the id of the user you want to fetch its personal information.
user.personalinformation_set.all() # This will return you a QuerySet with all the related instances of PersonalInformation class.
user.personalinformation_set.get(id='') #To get a specific one or you may use a filter to get a filtered QS.
If you want, you can use the related_name attribute for ForeignKey class in order to set a different name from personalinformation_set.
I recommend you too to read the Django documentation, it's really well explained and clear I think:
https://docs.djangoproject.com/en/2.2/topics/db/examples/many_to_one/
As I've seen in a comment, you may also think to use a OneToOne relation instead of ForeignKey if you only expect one instance of PersonalInformation per User. The documentation is at:
https://docs.djangoproject.com/en/2.2/topics/db/examples/one_to_one/

Do i need to update AUTH_USER_MODEL in my settings.py?

I am creating my own users, Restaurant and Customer. I have extended the AbstractUser class and then created a OneToOneField field for each user. I am wondering if I need to add the AUTH_USER_MODEL in my settings.py. And also wondering what that does exactly...
What I was planning on doing was adding to my settings.py:
AUTH_USER_MODEL = 'myapp.Customer','myapp.Restaurant'
Do I have the right idea here?
My models.py:
class User(AbstractUser):
is_restaurant = models.BooleanField(default=False)
is_customer = models.BooleanField(default=False)
class Restaurant(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
restaurant_name = models.CharField(max_length=50)
def __str__(self):
return self.restaurant_name
class Customer(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
address = models.CharField(max_length=200)
def __str__(self):
return self.user.get_full_name()
No. AUTH_USER_MODEL isn't expecting a tuple, so this won't work.
In any case, Restaurant and Customer are not your user model; your subclassed User is. That's what you should be putting in that setting.
I would suggest create single user table instead of three different tables and add type as restaurant, customer, admin etc. And add only one table into settings file. this won't lead any further issues authentication etc. Having single user table is always robust. In your case having three tables seems not good to maintain.
========== UPDATE ===========
Create model for user named as CustomUser (or name which you feel better) and extends to User Model of Django using AbstractBaseUser,PermissionsMixin. like
class CustomUser(AbstractBaseUser): have all fields which user table has already. and add your desired table to bifurcate type of restaurant and
customer have type field with choices option.
For further help you can check section https://docs.djangoproject.com/en/1.11/topics/auth/customizing/#substituting-a-custom-user-model

Categories

Resources