I have several models, one of which uses a database on an external user. The database is defined in the settings file. How would I do the following for this model?
class ItemMaster(models.Model):
id = models.AutoField(primary_key=True) # auto-added anyways
guid = models.CharField(max_length=36, unique=True)
db = using('avails') # <== use this database
Note: the only thing I care about here is being able to do a get_queryset query. I don't need a full database router.
As Shang Wang has pointed out in the comments, using a database router is the best solution.
Whenever a query needs to know which database to use, it calls the
master router, providing a model and a hint (if available). Django
then tries each router in turn until a database suggestion can be
found. If no suggestion can be found, it tries the current _state.db
of the hint instance. If a hint instance wasn’t provided, or the
instance doesn’t currently have database state, the master router will
allocate the default database.
If you are not keen on using routers, one alternative is to call the using on each and every queryset API call to define which database to use.
ItemMaster.objects.using('avails').all()
itemmaster.save(using='avails')
itemmaster.delete(using='avails')
But this will not be very DRY. A second alternative is to create a custom manager and override each of the methods to add the using('avails') fragments. But this involves writing a lot more code that you would write when using a router.
Related
I will add a procedure to a django app where I need to store data but only for a few hours, also I don't wat to add another table to my db schema (which is kind of big), I'm thinking to use redis for the task, in the end what I want to achieve is to have a Transfer model, and I want this model always be using another database for its CRUD operations.
Example:
Transfer.objects.all() # Always be the same as Transfer.objects.using('redis').all()
OtherModel.objects.all() # Always use default DB
# Same for save
transfer_instance.save() # Always translate to transfer_instance.save(using='redis')
other_instance.save() # Work as usuall using default DB
How can I achieve this? I don't mind using obscure trickery as long as it works.
Thanks!
You will need to use a Database Router to achieve what you need.
Here is the official documentation for Using Database Routers
Let's say I have two users, A and B, with IDs 1 and 2 (respectively). Further, let's assume I have two datasources configured: X and Y.
How could I isolate ALL queries issued by user A to datasource X, and all by B to Y for some given remote method? For example, say that A wants to run 'find' for some model via the API - how could I make sure that the only results A will get are those which are accessible through datasource X?
I'm not sure I entirely understand why you would decide a datasource based on the current user, but in any case, I'm not sure you can do that with LoopBack ... at least, not easily. LoopBack is a model-driven framework - everything derives from the model. As such, all API endpoints go through a model (although you can set up custom routes). And each model is connected to a single datasource.
So, if I hit /api/Widget/13 there is no way to make that findById() call switch between two datasources, it will always hit whatever datasource the model is connected to.
Okay, that all said, the solutions I see are to:
Create a "dispatcher" and have that model do the appropriate thing.
Create a custom remote method on your existing model and do the decision making there and the find, etc on the correct datasource.
In either case, it's not straightforward, and not built-in. FYI, if you need to get the datasource you can access it from the LoopBack application object: MyModel.app.datasources.ds1
I am a newbie with Tastypie and it is wonderful the way you can achieve CRUD operations with it so quickly. But I would like to implement other kind of web services where the return value is other than a model. For example, if I had a simple model like this
class User(models.Model):
name = models.CharField(max_length=20)
age = models.PositiveSmallIntegerField()
and wanted to get the average age of all users via /api/v1/user/avg_age, how should I do it? Perhaps it is something related to Django URLs more than Tastypie but I am lost at this moment. So, the question is where/how should I define my custom REST web services?
Thanks in advance
You can add the method to the model itself or put it in a service layer. After doing so you can easily add the value to the resource with a dehydration cycle.
Another option, which will allow filtering on the value, is to implement a model holding this data, e.g. a UserStatistics model. You can then add a foreign key relationship or create a stand-alone resource.
Because data won't likely change a whole lot and these calculations are more expensive I would encourage you to create a cronjob or task for such a model, only executing database writes periodically
Our app sets the is_active field in User Model to False to represent a deleted user.
What's the best practice for excluding the deleted users (where is_active=False) from each and every access to the user table?
Please consider the following:
1. The app is already written, so we'd appreciate as minimum code changes as possible.
2. The app uses: request.user, get_object_or_404() and of course User.objects, so the solution has to take all of them into account.
From the research I've done, I found:
1. Proxy model: will force me to make a lot of changes in the code; I don't know how it works with request and get_object_or_404().
2. contribute_to_class: Can it be used to override objects manager or to just to add a new one? Is it safe?
3. Middleware changes: I don't want to get into this. Too risky for me.
Is there an elegant way for doing this?
There's no way to do this, nor should you try. The only way to limit every action to a selection of model instances is to limit the default queryset, which then effectively orphans the excluded instances, providing no way to access them ever again. The Django docs explicitly warn against this behavior.
If you override the get_query_set() method and filter out any rows, Django will return incorrect results. Don't do that. A manager that filters results in get_query_set() is not appropriate for use as an automatic manager. (emphasis mine)
"Automatic" managers are basically the same as the default manager. It's what's used for related fields, in the Django admin, and in countless other areas of the Django machinery. If you limit the default manager, you limit everything across the board.
Now, there's other options for quickly accessing the limited queryset; they simply aren't "automatic", meaning you still must make a point of using them instead of just having everything happen by magic. However, "magic" violates one of the core Python tenants in this respect: explicit is better than implicit. Limiting the User queryset by default is an implicit action. Filtering the queryset manually, referencing a custom manager method, or using a subclass of User are all explicit actions, and preferable as a result.
I have a Django app that works well for me, but currently has no notion of user: I am the only one using it, and I would like to change this...
Except for the admin views, the logged-in user should not have access to the data created by other users. There is no shared data between users.
I suppose I have to add a user foreign key to all the models I created. Correct?
Is there a simple way to implement the filtering based on request.user? Can this be done more or less automatically, or do I have to go through all the code to check each and every query done on the database?
I have written the code using TDD, and I intend to follow up... What are the best strategies to ensure that user-filtering is implemented correctly, e.g. that I did not forget to filter an existing query? I suppose I can write tests that show that a particular query is not yet filtered, and implement the filter. But what about the queries that I will write later? Is there a way I can assert that all existing and future queries return objects that only belong to the current user?
Thanks.
Yes, you'll need to add a User FK. Don't forget you'll have to migrate your database tables - either manually, or via a tool like South.
One way of implementing the filter would be to define custom Managers for your models, with a for_user method that takes the User as an argument: something like:
class ForUserManager(models.Manager):
def for_user(self, user):
return self.filter(user=user)
Now you can use this manager - subclassed and/or with a mixin as necessary - on all your models, and remember to use objects.for_user(request.user) everywhere.
This will make testing easier too - your test could monkeypatch that for_user method so that it sets a flag or a counter in a global variable somewhere, and then test that it has incremented as expected.
Edit in response to comment No, as you suspect, that won't work. It's not even that everyone will necessarily get the last-logged-in user: it's that Managers are class-level attributes, and as such are reused throughout a process, so any request served by that server process will use the same one.