Can I dynamically create db instances for use with mongo_prefix? - eve

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

Related

Getting data out of devise

I'm migrating away from rails. I will be using the same domain, so I'll get the _session_id cookie that rails uses and I can bring over the old sessions table.I would like to use this to extract data (the user_id) from the old session. I can not tell how to do this outside of rails.
Within a controller there's current_user of course or session["warden.user.user.key"], but how can I take the id, decrypt the data in the table, and pull stuff out on my own (besides running the old rails application and creating a route on that that returns the info I need and hitting it from my new application)?
I'm not entirely sure this is the best way, but I was intrigued so went down the rabbit hole. This works for my 4.1.10 app where sessions are stored in the cookie. You'll want to look at action pack's EncryptedCookieJar class and active support's CachingKeyGenerator and MessageEncryptor classes for details.
Obviously you'll need to replace the two strings that start "THE VALUE…".
key_generator = ActiveSupport::KeyGenerator.new('THE VALUE OF SECRET_KEY_BASE FROM config/secrets.yml', iterations: 1000)
caching_key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
caching_key_generator.generate_key('encrypted cookie')
caching_key_generator.generate_key('signed encrypted cookie')
secret = caching_key_generator.generate_key('encrypted cookie')
sign_secret = caching_key_generator.generate_key('signed encrypted cookie')
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: ActionDispatch::Cookies::NullSerializer)
session_value = CGI::unescape('THE VALUE OF THE SESSION COOKIE')
serialized_result = encryptor.decrypt_and_verify(session_value)
result = Marshal.load(serialized_result)
The result, for me, is a hash that looks exactly the session hash in Rails.
If it doesn't work for you, you may be using a different serializer so need to replace Marshal.load with whatever you need. Just take a look at serialized_result and see.

Rails Limit Model To 1 Record

I am trying to create a section in my app where a user can update certain site wide attributes. An example is a sales tax percent. Even though this amount is relatively constant, it does change every few years.
Currently I have created a Globals model with attributes I want to keep track of. For example, to access these attributes where needed, I could simply do something like the following snippet.
(1+ Globals.first.sales_tax) * #item.total
What is the best way to handle variables that do not change often, and are applied site wide? If I use this method is there a way to limit the model to one record? A final but more sobering question.......Am I even on the right track?
Ok, so I've dealt with this before, as a design pattern, it is not the ideal way to do things IMO, but it can sometimes be the only way, especially if you don't have direct disk write access, as you would if deployed on Heroku. Here is the solution.
class Global < ActiveRecord::Base
validate :only_one
private
def only_one
if Global.count >= 1
errors.add :base, 'There can only be one global setting/your message here'
end
end
end
If you DO have direct disk access, you can create a YAML config file that you can read/write/dump to when a user edits a config variable.
For example, you could have a yaml file in config/locales/globals.yml
When you wanted to edit it, you could write
filepath = "#{Rails.root}/config/locales/globals.yml"
globals = YAML.load(File.read("#{Rails.root}/config/locales/globals.yml"))
globals.merge!({ sales_tax: 0.07 })
File.write(filepath) do |f|
f.write YAML.dump(globals)
end
More on the ruby yaml documentation
You could also use JSON, XML, or whatever markup language you want
It seems to me like you are pretty close, but depending on the data structure you end up with, I would change it to
(1+ Globals.last.sales_tax) * #item.total
and then build some type of interface that either:
Allows a user to create a new Globals object (perhaps duplicating the existing one) - the use case here being that there is some archive of when these things changed, although you could argue that this should really be a warehousing function (I'm not sure of the scope of your project).
Allows a user to update the existing Globals object using something like paper_trail to track the changes (in which case you might want validations like those presented by #Brian Wheeler).
Alternatively, you could pivot the Global object and instead use something like a kind or type column to delineate different values so that you would have:
(1+ Globals.where(kind: 'Colorado Sales Tax').last) * #item.total
and still build interfaces similar to the ones described above.
You can create a create a class and dump all your constants in it.
For instance:
class Global
#sales_tax = 0.9
def sales_tax
#sales_tax
end
end
and access it like:
Global.sales_tax
Or, you can define global variables something on the lines of this post

How checking value from DB table?

I want create a simple checking value from database. Here is my code:
def check_user_name(name, email)
db_name = Customers.find_by_name(name).to_s
db_email = Customers.find_by_email(email).to_s
if name == db_name && email == db_email
return 'yes'
else
return 'no'
end
end
But I have allways 'no' variant....why ?
Because you are calling to_s on your Customers model and not actually getting the name. The two fetch lines you have should be:
Customers.find_by_name(name).name.to_s # to_s probably not necessary if you know this field is a string
Customers.find_by_email(email).email
But, you're making two separate requests to the database. I don't know what the purpose of this is (as you could be selecting two different Customers) but you could do:
if Customers.where(name: name, email: email).exists?
"yes"
else
"no"
end
Since you are, however, selecting by name and email - I would highly recommend that you make sure those fields are indexed because large tables with those requests will bog the server and make that route rather slow (I would actually recommend that you pursue other routes that are more viable, but I wanted to mention this).
When you give Customers.find_by_name(name), you will not get name of a customer. Actually it will return activerecord object, so from this object you need to get the name and email of a customer, like below,
name = Customers.find_by_name(name).name
email = Customers.find_by_email(email).email
Now you will get the exact name and email of matched data from DB.

Rails - trying to understand a piece of code

I am working with some code I found online:
def person_path(options)
# This is where the path of the query is constructed.
path = "/people/" # For this section of the LinkedIn API
if id = options.delete(:id)
path += "id=#{id}"
elsif url = options.delete(:url)
path += "url=#{CGI.escape(url)}"
else
path += "~"
end
end
I am not completely certain what it does. what I am trying to do is have it construct a string something like this: http://api.linkedin.com/v1/people/~:(current-status) which I got from the LinkedIn developer docs here: https://developer.linkedin.com/documents/profile-api
Any thoughts on what I should pass this functions and how exactly it accomplishes what it does?
Thanks!
Whilst it's not stated what 'options' is, it's extremely common to pass in options to a method as a Hash of key-value pairs in Ruby, so I'd say that options is just that (with 99% certainty). This is the part that's key to understanding the rest of the code.
I believe that the #delete method on hash is being used in order to pull out the key-value pair and assign the value in one move, whilst taking advantage of the returned object's "truthiness".
http://www.ruby-doc.org/core-1.9.3/Hash.html#method-i-delete
And by "truthiness", I mean that in Ruby, all objects evaluate to 'true' except 'nil' and 'false'.
The rest is simple if-else control flow logic that you will have seen in any other language, so I hope this makes sense.
This just creates a path of the form "/people/id=foo" or "/people/url=foo_with_%_escapes" if it finds id or url in the options. As a side effect, it deletes the one it finds from the options. If it doesn't find either one, it gives "/people/~"

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

Resources