django admin permissions to modify model's attributes - django-admin

we are using django to develop a customer-management application, and we need to set permissions to an agent whether he/she can edit the customer's attributes ().
for example,
if i have a model:
class Customer(models.Model):
# basic information
name = models.CharField(max_length=150) # the name of this customer
date = models.DateField(auto_now_add=True) # the date that this customer is created
# personal information
citizen_id = models.BigIntegerField(blank=True)
phone = models.BigIntegerField(max_length=20, blank=True)
work = models.CharField(max_length=100, blank=True)
address = models.CharField(max_length=300, blank=True)
bank_card = models.BigIntegerField()
# installation detail
primary = models.IntegerField(default=0)
secondary = models.IntegerField(default=0)
region = models.ForeignKey(Region) # the region that this customer currently lives in
type = models.ForeignKey(Type) # the installation type
group = models.ForeignKey(Group)
STATUS_CHOICES = (
('Active', 'Active'),
('Inactive', 'Inactive'),
('Transfered', 'Transfered'),
('Closed', 'Closed'),
('Suspended', 'Suspended'),
('Cancelled', 'Cancelled'),
)
status = models.CharField(max_length=40, choices=STATUS_CHOICES)
and I want to be able to set the permissions for editing some of the fields, but the current permission system only allow you add/change/delete a model instance, where the "change" allows an user to edit all the attributes in that model, which is not what we really want.
user A can edit phone, address, work and citizen_id
user B can only edit phone and address,
user C can edit citizen_id, .....
etc...
and I want to be able to set different permissions
Is it possible to make this happen? It'd be really helpful if I could use the django admin system to manage agents and customers.
=======================
thank you so much for FallenAngel's reply.
I think that's exactly what we want.
this is what I've tried,
in admin.py
class CustomerAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, extra_context=None):
agent = Agent.object.get(user=request.user)
permitted_fields = agent.permitted_fields # assume i have this setup...
self.readonly_fields = get_not_permitted_fields(premitted_fields) # assume I have this function written somewhere
return super(CustomerAdmin, self).change_view(request, object_id,
extra_context=None)
this works exactly that way I want: for the not permitted fields, set them to readonly...
thanks again,

That is possible... You must use django admin methods as they described in here... You must define add_view, change_view and changelist_view functions...
You must also find a way to group users (groups might be a good way, or you can use permissions). I create a model that extends User model, but you can find your own way...
Second write basic add, change and listview founctions like:
class CustomerAdmin(admin.ModelAdmin):
list_display= ["fields that will be used for all users"]
def changelist_view(self, request, extra_context=None):
if request.user == "type a":
self.list_display.extend["list of other fields"]
return super(CustomerAdmin, self).changelist_view(request, extra_context)
You must specify add, change (and changelist if required) views and handle each user type. Then Django will show related fields for the user. You extend list_display, fileds, exclude, list_filter and such django admin field display methods...

Related

Can I dynamically create db instances for use with mongo_prefix?

mongo_prefix looks ideally designed for simple and effective data separation, it seems though you need to pre-define your available prefixes in settings.py. Is it possible to create a new prefix dynamically - for example to create a new instance per user on creation of that user?
The authentication base class has the set_mongo_prefix() method that allows you to set the active db based on the current user. This snippet comes from the documentation:
Custom authentication classes can also set the database that should be used when serving the active request.
from eve.auth import BasicAuth
class MyBasicAuth(BasicAuth):
def check_auth(self, username, password, allowed_roles, resource, method):
if username == 'user1':
self.set_mongo_prefix('USER1_DB')
elif username == 'user2':
self.set_mongo_prefix('USER2_DB')
else:
# serve all other users from the default db.
self.set_mongo_prefix(None)
return username is not None and password == 'secret'
app = Eve(auth=MyBasicAuth)
app.run()
The above is, of course, a trivial implementation, but can probably serve a useful starting point. See the above documentation link for the complete breakdown.
Ultimately the answer to the question is that your prefixed database will be created for you with defaults if you have not first specified the matching values in your settings.py. In cases where you cannot put the values in settings.py (probably because you don't know them at the time) happily you can add them dynamically later; trivial example below.
def add_db_to_config(app, config_prefix='MONGO'):
def key(suffix):
return '%s_%s' % (config_prefix, suffix)
if key('DBNAME') in app.config:
return
app.config[key('HOST')] = app.config['MONGO_HOST']
app.config[key('PORT')] = app.config['MONGO_PORT']
app.config[key('DBNAME')] = key('DBNAME')
app.config[key('USERNAME')] = None
app.config[key('PASSWORD')] = None
and then later, e.g. in check_auth(...):
add_db_to_config(app, 'user_x_db')
self.set_mongo_prefix('user_x_db')

Ruby on Rails / ActiveRecord: Updating records with first_or_initialize causes RecordNotUnique

I'm currently getting user data from a SAML assertion and creating users in a local DB based on that info:
mapped_role = map_role user_role
user = User.where(email: auth_attrs.single('Email')).first_or_initialize do |u|
u.firstname = auth_attrs.single('First Name')
u.uid = auth_attrs.single('UID')
u.provider = resp.provider
u.role = mapped_role
end
This works well enough, but when the user's details change (for instance, their role changes), that data doesn't get updated in the DB. What I've tried doing is moving the role assignment out of the do block (on the user object returned by first_or_initialize) and then calling a follow-up user.save, but this results in a pretty red screen informing me that the column 'email' isn't unique. I don't want to be creating a new record here, just updating an existing one. Is there a better pattern to be using here?
Edit: I've tried the various approaches laid out here, but they result in the same SQLite3 error. It seems like I'm missing something there.
Edit2: It looks like this might be due to Devise trying to do something behind the scenes with an email field of its own(?).
I think I would go about it like so
mapped_role = map_role user_role
# find the user or initatiate an new un-persisted user
user = User.find_or_initialize_by(email: auth_attrs.single('Email'))
attributes = {firstname: auth_attrs.single('First Name'),
uid: auth_attrs.single('UID'),
provider: resp.provider,
role: mapped_role}
# attempt to update the above record with the appropriate attributes
# this will set the attributes and fire #save
if user.update(attributes)
# successful
else
# handle validation errors
end
This way there is no need for logical handling of users that are already persisted and new users.

Django-Guardian Permissions - Set permissions for objects in a on-to-many relationship

I have the following models:
Project:
class Project(models.Model):
project_name = models.CharField(max_length=100, unique=True)
SearchCodes:
class SearchCode(models.Model):
project = models.ForeignKey(Project, on_delete=models.CASCADE)
search_code = models.CharField(max_length=3)
search_code_modifier = models.CharField(max_length=1)
class Meta:
unique_together = ('project', 'search_code', 'search_code_modifier',)
From the Django-Guardian docs I am able to set permissions in the Django admin. Then for a Project I can check/restrict users in a view like so:
def project_edit(request, pk_project):
project = get_object_or_404(Project, pk=pk_project)
project_name = project.project_name
user = request.user
# Check user permission at the object level by passing in this specific project
if user.has_perm('myApp.change_project', project):
...do something
Now here is where I am stuck. How do I set permissions on ANY SearchCodes that are related to a Project in the Django-Admin? I don't want to set object-level permissions on the SearchCodes - That would just be every instance of SearchCode. Rather I need to be able to set in the Admin:
You can view every SearchCode that is related to this Project BUT not any other SearchCodes.
Please let me know if I need to be more specific - I have tried to keep this as generic as possible.
Thank you - any help or pointers would be greatly appreciated.
EDIT:
I used the Admin-Integration example to set up the Project object-level permissions in Django-Guardian.
It feels like at this point there must be some way to set the permissions for all SearchCodes that are related to this particular instance of Project while I am in the Object-Permissions page for this Project.
Django-guardian is object level permission application, so assign permission on object which carry permission and check on that object for permission. In you particular example you have to write something like that to check permissions for code through project (permission carry object):
def code_edit(request, pk):
code = get_object_or_404(SearchCode.objects.select_related('project'), pk=pk)
project = code.project
project_name = project.project_name
user = request.user
# Check user permission at the object level by passing in this specific project
if user.has_perm('myApp.change_project', project):
...do something

rails can you reference a model within a model?

I seem to have run into a problem with trying to use a model in another model in Rails.
I am pulling a list of users from Active Directory with LDAP in a dropdown. I want to parse the cn that I get from Ldap into a firstname and lastname.
The problem I am running into is that I need to find a record in the users model. The parsing is being done in observations.rb.
Observation.rb:
def parse_employee
#emp_name = '' #initialize
self.employee_raw = self.employee_raw[2...-2] # get rid of the quotes and brackets
#emp_name = self.employee_raw.split(' ') # split first/last names
#emp_first_name = #emp_name[0] #Grab the first name
#emp_last_name = #emp_name[1] # grab the surname
#user = User.where("last_name like ?", #emp_last_name)
self.employee_id = #user.id
end
I've played with this quite a bit and it appears that I can't reference other models from within a model.
To sum up, what I am trying to do is
1. Have the user select the appropriate person from a dropdown that is pulled via LDAP from active directory.
2. Use the first and last names to find the appropriate user in my user table (Right now I'm just trying to get it to work with the last name as that is unique enough)
3. When I find the correct user in the user table, enter that id in the employee_id field in my observations table.

Rails updating attribute security: use a callback or attr_accessible?

I've got a website model that requires a user to verify ownership of the website.
Thanks to stack overflow, I was able to find the solution for user ownership verification here: Validate website ownership in rails
After the model passes the verification test there is a verified attribute that gets set to true.
The problem I'm running into is when the user wants to edit attributes of his or her website, he or she could easily change the name of the domain while the verified attribute remains true, thus allowing the user to create website objects without verifying ownership.
I can think of two ways to solve this:
1. Have a callback that changes the verification to false if the domain name of the website gets changed.
2. Allow attr_accessible for the domain upon the creation of a new object but not when updating it.
I'm stumped as to how to implement either of these practically.
Callbacks and Active Record Dirty methods are definitely the way to go for this type of situation.
before_update :set_domain_status
def set_domain_status
self.verified = false if self.domain_changed?
end
_changed? can be added to any attribute, returning true if the value has changed from that originally loaded from the database.
I think your Option#1 is the best route. Otherwise you get into the business of trying to reconcile create and update actions - which you would need to do to handle Option #2.
You could override the setter for domain name and then perform custom logic, like so:
In your Model:
def domain=(the_domain)
raise ValidOwnerRequired if verified? && domain != the_domain
# if we got here then the this record is new and this is a creation attempt
#require_domain_verification = true
# do other stuff here..
end
def require_domain_verification?
#require_domain_verification == true
end
And then have an observer for that model:
def after_save(record)
if record.require_domain_verification?
SomeMailer.deliver_domain_verification(record)
end
end
Something like that...
Cody, your answer got me on the right track. Thanks a lot!
This is what I went for in my case:
def domain=(the_domain)
if domain != the_domain
self[:domain] = the_domain
self[:verified] = false
end
end
It works just fine.

Resources