Open tino097 opened 5 months ago
Hi @tino097, sorry for delay
Your error is caused by data_factory
. It must be a class itself, not the object. And parameters need to be passed as data_settings
. So, the full version of my-users
is:
{
'my-users': lambda n, p, **kwargs: cu.ApiListCollection(
n,
p,
data_factory=cu.ApiListData
data_settings={"action": 'user_list'})
}
I'll rewrite readme and add examples of collection creation in the beginning, before diving into the internals. If you can give me a couple of use-cases, it would be a good material for documentation
And regarding UserCollection at the end of your code snippet. Most likely, you want to register a collection, that uses a custom serializer, and assigning data_settings is accidental
For such a situation, where you only want to replace a factory, you can omit the constructor and assign the factory to the corresponding attribute:
class UserCollection(cu.ApiCollection):
SerializerFactory = UserSerializer
Signature of collection constructor is def __init__(self, name: str, params: dict, **kwargs):
. The important part here is kwargs
- note, it's not data_settings
.
For example, when you build a collection with Collection(n, p, data_settings={})
, I imagine, you want to access this data_settings
, right? In this case, data_settings
is kept inside kwargs:
class MyCollection(cu.ApiCollection):
def __init__(self, name: str, params: dict, **kwargs):
super().__init__(name, params, **kwargs)
print("THIS IS DATA SETTINGS ->", kwargs.get("data_settings"))
And, with these comments, we can try building your collection. If you just want a user list that filters user using q
parameter and displays only id, name, imail, fullname; you need the following code:
# ICollection
def get_collection_factories(self) -> dict[str, CollectionFactory]:
return {
'my-users': MyUserCollection,
}
##### your implementation of UserSerializer is left unchanged ####
# ApiList and Api collections just override the data factory. We are going to do it
# ourselves, so there will be no difference if we just use a simple collection as a base class
class MyUserCollection(cu.Collection):
# Data.with_attributes defines the anonymous class with a specific attribute
# overriden. If you are not going to use your custom data factory
# elsewhere, this is the shortest possible syntax
DataFactory = cu.ApiListData.with_attributes(action="user_list")
SerializerFactory = UserSerializer
BTW, in your initial implementation, instead of cu.ApiListData(action='user_list'))
which created an object and caused an error, you could use cu.ApiListData.with_attributes(action='user_list'))
which would create a new class with fixed value of action
.
Thanks @smotornyuk
Hey @smotornyuk
from ckanext.collection import internal, types
ImportError: cannot import name 'internal' from 'ckanext.collection'
I've pulled latest from master
Thanks. I forgot to commit internal.py. Now it's added to the repo, so issue must be fixed in latest commit
BTW, I'm rewriting the documentation. At the moment, I finished pages above the red line Everything below the red line still in draft state.
Mainly, I'm trying to explain things gradually with more examples. And there is one change: instead of importing everything like import ckanext.collection.utils as cu
, it's recommended to import shared module and access items from it.
from ckanext.collection.shared import collection, data, serialize
#and use it like below
collection.Collection
data.ApiSearchData
serialize.CsvSerializer
To confirm, if i want to have a custom data, i would need to create my own action where i would get desired information?
Or if i could use the ModelCollection
for that purpose?
Using ModelCollection is more efficient, but there are certain disadvantages.
If you use ModelCollection with a specific model from CKAN, you'll get all the records from DB. Imagine that you create ModelCollection for the model.Package - you'll get public, private, deleted, and draft datasets at once. If you are showing this collection to admin only - it's ok. If you are filtering results from the collection before showing it to the anonymous user - it is also ok. But it's your responsibility to protect private data and show collection only to people with required access level
If you are using API action instead of the model, all restrictions are handled inside the action. If you use ApiSearchCollection that takes data from package_search
, package_search
is called with the current user and gives you back only datasets that are accessible by the current user.
So, the answer is:
My use cases are to get reports within CKAN, as example:
Report for datasets by user in orgs or groups
So there would be a filtering and restrictions over some of the data but im trying to figure it out what would be the right path
Thanks again
Cool, another example for the time, when I continue updating documentation.
Here you can use models directly. It doesn't sound like you'll be able to use API actions that collect data elsewhere, so creating them is not much value. Here's the code that creates a collection of every user. The collection contains the user's ID, name, full name, and all groups + organizations of the user.
To add filters to the collection, we need to modify the data factory. It will be converted into a standard class (instead of using .with_attributes
). The value of statement
is not changed. statement
defines the baseline of the source data - it must include as much data as possible. Filters will be applied by defining the statement_with_filters
method.
And here's the distribution of datasets created by users in different organizations/grops defined in the same manner
Here's implmenetation of the first collection using API action, just for reference. In this case, all the logic goes to action and collection becomes slim. You may find this style more readable, as you are more used for API actions
Im tryng to implement the
ICollection
interface with the following codeSo this collection is getting registered and im able to select on the explorer component but when selected im getting the following error
What im missing to set to initialize the ApiListCollection ?
Thanks