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

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

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')

ActiveRecord save all changes to has_many childs on parent save

I'm trying to update a parent object (Client) and associated child objects (License). It is possible that either of these objects is new, that's why I have to check for existing objects to update and if there aren't any I have to create them.
My code currently looks like this:
return nil unless params.has_key?('client_uid')
client = Client.find_by_uid(params['client_uid'])
if client.nil?
client = Client.new(uid: params['client_uid'])
end
client.app = params['application']
client.app_version = params['application_version']
...
licenses = params['licenses']
licenses.each do |licenseInfo|
next unless licenseInfo.has_key?('application')
license = client.licenses.find_by_application(licenseInfo['application'])
if license.nil?
license = License.new
# add license without instantly saving
client.association(:licenses).add_to_target(license)
license.application = licenseInfo['application']
end
license.options = licenseInfo['options']
...
end
client.last_seen = Time.zone.now
client.save
This is able to create new clients and update existing ones from the values in the params, as well as adding new licenses for the client (I will implement deleting old licenses later). The problem is that changes to existing licenses aren't saved when I call client.save.
I'm not sure if I'm doing something wrong, or if ActiveRecord really isn't able to handle a situation like this. I already searched for this problem and tried to explicitly specify autosave: true for the License class' belongs_to as recommended here, but this didn't have any effect.
In conclusion, my question is: What do I have to change to be able to save my Client together with all changes (new, changed, deleted) to his licenses?
EDIT:
I tried manually saving all licenses and putting this together in a transaction:
Client.transaction do
client.save
client.licenses.each do |license|
license.save
end
end
But this didn't work, either. Now I'm confused....
I think you need to add
accepts_nested_attributes_for :licenses
in Client model. This would help you to save the child objects
as mentioned in comment. Please refer this link
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Globally accessible class that connects to the db during startup only

I want to create a globally accessible class in my Rails project that will contain some active record result sets also.
#settings = Settings.first
#locales = Locales.all
$websiteSettings = WebsiteSettings.new(#settings, #locales)
Where should I be creating this class, is an initializer the best place?
I want to be able to access this class inside my controllers and views.
I understand that if the data changes in the database I will have to restart my rails application or update these global instances.
Put it in an initializer and set it as a global constant:
#config/initializers/settings.rb
module Settings
#settings = Settings.first
#locales = Locales.all
WEBSITE = WebsiteSettings.new(#settings, #locales)
end
This would allow you to access Settings::WEBSITE throughout your app.
This is only applicable if you're calling the data through ActiveRecord.
If you weren't accessing the data through the db, you'd be better using one of the many settings gems, or adding the settings directly to the Rails.config class:
#config/application.rb
config.settings.title = "Test" #-> Rails.configuration.settings.title
--
As an aside, I would strongly recommend you refactor your code; you're making 3 db calls to get a series of settings for your app.
Surely you could put all the settings inside the Settings model, scoped around different locales:
#config/initializers/settings.rb
module Settings
SETTINGS = Settings.where(locale: :en)
end

django admin permissions to modify model's attributes

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

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