New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change member_create and member_delete to accept object's id or name, and throw better errors #754
Changes from all commits
84fb67a
8075019
e66f150
6ec4a17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -417,28 +417,35 @@ def member_create(context, data_dict=None): | |
if 'message' in context: | ||
rev.message = context['message'] | ||
else: | ||
rev.message = _(u'REST API: Create member object %s') % data_dict.get("name", "") | ||
rev.message = _(u'REST API: Create member object %s') % data_dict.get('name', '') | ||
|
||
group = model.Group.get(data_dict.get('id', '')) | ||
obj_id, obj_type, capacity = _get_or_bust(data_dict, ['object', 'object_type', 'capacity']) | ||
group_id, obj_id, obj_type, capacity = _get_or_bust(data_dict, ['id', 'object', 'object_type', 'capacity']) | ||
|
||
group = model.Group.get(group_id) | ||
if not group: | ||
raise NotFound('Group was not found.') | ||
|
||
obj_class = ckan.logic.model_name_to_class(model, obj_type) | ||
obj = obj_class.get(obj_id) | ||
if not obj: | ||
raise NotFound('%s was not found.' % obj_type.title()) | ||
|
||
# User must be able to update the group to add a member to it | ||
_check_access('group_update', context, data_dict) | ||
|
||
# Look up existing, in case it exists | ||
member = model.Session.query(model.Member).\ | ||
filter(model.Member.table_name == obj_type).\ | ||
filter(model.Member.table_id == obj_id).\ | ||
filter(model.Member.table_id == obj.id).\ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this change seems like a regression a) we have the value already why not use it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I need to add this change because obj_id might be either the object's id or its name. I use obj.id to guarantee that we're always putting ids into the DB, so whenever we're looking for this object in the model.Member's table, we can simply filter to where model.Member.table_id == obj.id, not (model.member.table_id == obj.id OR model.Member.table_id == obj.name) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok that's cool |
||
filter(model.Member.group_id == group.id).\ | ||
filter(model.Member.state == "active").first() | ||
if member: | ||
member.capacity = capacity | ||
else: | ||
filter(model.Member.state == 'active').first() | ||
if not member: | ||
member = model.Member(table_name = obj_type, | ||
table_id = obj_id, | ||
table_id = obj.id, | ||
group_id = group.id, | ||
state = 'active', | ||
capacity=capacity) | ||
state = 'active') | ||
|
||
member.capacity = capacity | ||
|
||
model.Session.add(member) | ||
model.repo.commit() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import nose.tools as tools | ||
|
||
import ckan.model as model | ||
import ckan.logic as logic | ||
|
||
|
||
class TestMemberLogic(object): | ||
def test_model_name_to_class(self): | ||
assert logic.model_name_to_class(model, 'package') == model.Package | ||
tools.assert_raises(logic.ValidationError, logic.model_name_to_class, model, 'inexistent_model_name') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like this function
a) No doc string
b) If this is for use in the actions like you are using it then we should probably pass
model
as this is passed via the context. Now this is almost the always the same as we'd get fromimport ckan.model as model
but potentially could be different. We shouldn't break that logic.c) I'm not sure if it just adds confusion.
d) where it is used only some object types are allowed user and dataset so maybe just a quick bit of inline code might be simpler.
e) I see the point of checking that the object exists for creation but for deletion I'm not sure if this makes sense as cleaning bad data would not be possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a) True, I forgot about it.
b) When they could be different?
c, d) Yes, metaprogramming usually isn't that simple. I'm OK with this method specifically, as I don't think there's too much magic involved. But if there's only 2 possible cases, I agree in changing it. But I wouldn't do it inline, to keep it DRY.
e) I feel it's more user friendly to tell the user what's wrong with their request. If I try to delete a request and get an error back, it would be great if it told me that the problem is that I probably sent an invalid model name, and not some generic error. Also, it's easier to reuse the method for both create and delete :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b) theoretically they could be different in practice probably never. but maybe an extension might supply it's own.
e) I still think that if user fred is a member of a group but does not exist as an actual user that I should be able to remove them as a member - this is just about coping with broken data which will exist in some ckan instances. For creation the test is really sensible and needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b) I would feel it really odd that CKAN models weren't in ckan.model. That would probably break a lot of stuff. If you don't now anyone that might need this now, I feel it would be better to keep it as it is.
b) But that works with this code. I only raise an exception if you call something like
model_name_to_class('okapi')
, throwing an exception because there's nockan.model.Okapi
. It doesn't check if the user exists or not, just that its class does.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b1) the model is in context['model'] why? don't ask me but that is where we get it from so we should either kill it everywhere or stick with it. For now let's stay as we are until we work out why.
b2) ok I'll read the code again I've been hungover today due to sunday being the first sunny day for weeks :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b1) I think @kindly is the reason but I believe that it was part of an earlier code cleanup - ckan is old and imho had some major design flaws and the
action(context, data_dict)
although problematic was a change for the better.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
b1) the reason it is like that is to make the actions thread safe functions which is a good thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I don't get it. Why would that make the methods thread safe? Shouldn't ckan.model be readonly? And, even if it's not, do CKAN do something different than simply context['model'] = ckan.model somewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean that we only use what we're given so the caller can decide what we see so maybe thread safe isn't quite the right expression. I think that we never get a different model but we can end up with a different database session on occasions so we should use
context['session']
not `context['model'].Session. I only know a small amount of where the session issues arise but they do ie we need the other session.. This is something that maybe could be changed/documented better.Keep these questions coming. The only thing is that they can end up a bit contained in github and at a point need to break out into the dev-list so everyone gets to have their input. Maybe this is one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was thinking about this. I'll send a message to ckan-dev and we can keep going there 😄