Can I make a tastypie modelresource field read-only? - tastypie

I have a Tastypie ModelResource which gets its fields from a regular Django Model. I would like to make certain fields read-only on the Tastypie resource, even though they are writeable in the underlying model. Is this possible to accomplish in a simple way?
I've tried the following to no avail:
def __init__(self, **kwargs):
super(ModelResource, self).__init__(**kwargs)
for f in getattr(self.Meta, 'read_onlys', []):
self.fields[f].read_only = True

Normally I would do that sort of thing in the hydrate/dehydrate process.
There are probably other ways,
def hydrate(self, bundle):
if bundle.obj.pk:
bundle.data['somefield'] = bundle.obj.somefield
else:
bundle.data.pop('somefield', None) # no KeyError if 'somefield' missing
return super(MyResource, self).hydrate(bundle)

The problem is that BaseModelForm overwrites self.instance when doing validation.
And this ofcourse happens regardless if a Tastypie field has readonly set or not (that onfly affects Tastypie's own hydrate, nothing else).
So I ended up writing this: https://gist.github.com/thnee/8522224

Not sure whether you need this anymore, but here's the link to official documentation related to readonly fields.
example:
class ResourceA(ModelResource):
read_only_field = fields.DateTimeField('attribute', readonly=True)
Hope this helps someone.
Thanks

Related

Rails 4 with CanCan: unknown attribute error after including load_and_authorize_resource

I'm working in Rails 4 and have gotten CanCan to work well with instructions from this issue, except for one use case that I think might be relatively common.
I have a Comment model, which has_many :comments, through: :replies for nested comments. All of this is working well, until I add load_and_authorize_resource to my comments controller. The problem seems to stem from a hidden field sending an optional :parent_comment_id attribute to my create action.
I've permitted this attribute via strong parameters:
def comment_params
params.require(:comment).permit(:content, :parent_comment_id, :post_id, :comment_id, :user_id)
end
So that I can create the association if a :parent_comment_id is included:
if comment_params[:parent_comment_id] != nil
Reply.create({:parent_comment_id => comment_params[:parent_comment_id], :comment_id => #comment.id})
end
But once I add load_and_authorize_resource, I get an unknown attribute error for :parent_comment_id. What am I missing?
Solution came to me in my sleep. Here's what I did to solve the problem:
The only reason comment_params wasn't normally having a problem on create, was because I was excluding the extra :parent_comment_id parameter, like this:
#comment = post.comment.create(comment_params.except(:parent_comment_id))
When CanCan used the comment_params method however, it did no such sanitation. Hence, the problem. It would have been messy to add that sanitation to CanCan on a per-controller basis, so I did what I should have done all along and instead of passing the :parent_comment_id inside :comment, I used hidden_field_tag to pass it outside of :comment and accessed it through plain, old params.
I hope this helps someone else who makes a similar mistake!

Django Tastypie, Remove Elements From ManyToMany Fields

I am using Tastypie, Django for my project.
To Update a many to many field I have used save_m2m hook.
def save_m2m(self, bundle):
for field_name, field_object in self.fields.items():
if not getattr(field_object, 'is_m2m', False):
continue
if not field_object.attribute:
continue
if field_object.readonly:
continue
related_mngr = getattr(bundle.obj, field_object.attribute)
related_objs = []
print bundle.data[field_name]
for related_bundle in bundle.data[field_name]:
try:
stock = Stock.objects.get(nse_symbol = related_bundle.obj.nse_symbol)
print stock.__dict__
except Stock.DoesNotExist as e:
dataa = {"error_message": e}
raise ImmediateHttpResponse(response=HttpBadRequest(content=json.dumps(dataa), content_type="application/json; charset=UTF-8"))
related_objs.append(stock)
related_mngr.add(*related_objs)
Now I want to remove elements from the same many to many field.
How should I achieve this. Do I have to send a patch request or delete request and how to handle this.
I am begineer in tastypie. I googled it some time and I couldn't find a proper way. Please guide me how to complete this.
Thanks.
I've thought a lot about handing m2m relationships, since most of our app depends on m2m links.
I've settled for the approach of an update method. Pass in the all the references of the relationships you want changed (add and remove), then update the db accordingly. We only pass in the changed values, since if you have a paginated list, you only want to update the items the user has identified. Generally I use a custom hook for this defined in override_urls.
I used to have a separate add and remove method, which worked well until we changed the gui and allowed users simply to change checkboxes. In that approach having an update method was much more useful. You'll have to decide on which method suits your application the best.

Prevent form manipulation in Lithium/mongoDB

I'm writing my first community page with Lithium and mongoDB. I really like the schema-less way of mongo, but there is one problem making it impossible working without a schema:
For instance we have a simple form like this:
<?=$this->form->create();?>
<?=$this->form->field('name',array('label' => 'Topic title'));?>
<?=$this->form->field('text',array('label' => 'Content'));?>
<?=$this->form->submit('create');?>
which will be even simpler saved by this:
if($this->request->is('post')) {
$board_post = BoardPosts::create($this->request->data);
$board_post->save();
}
Now it's possible for everyone to add some form inputs by DOM manipulation with Firebug, Developer Tools etc. Of course that it might be some sensless fields in the database, but maybe someone adds a field, that is really used.
The only way to prevent this, is creating a schema in model. But for me this makes the whole idea of a schema-less database useless, doesn't it? And how to make schemas for different situations/actions, when some fields must not occur?
The Model::save() method accepts a 'whitelist' param in its options. See http://li3.me/docs/lithium/data/Model::save()
$whitelist = array(
'title',
'text'
);
$post = BoardPosts::create();
$post->save($this->request->data, compact('whitelist'));
You can also define protected $_schema in your Model and set protected $_meta = array('locked' => true); which will automatically set the whitelist to the fields defined in your schema. However, it is a good idea to define the whitelist in your controller to avoid attacks like you describe.
This problem is called a mass-assignment vulnerability and exists in many frameworks if developers are not careful.

Zend Framework / Form Element is rendering as a text box rather than a dropdown box

I have the following in a config.ini file: (Zend_Form_Element)
site_status.name = "site_status"
site_status.type = "select"
site_status.label = "Status"
site_status.options.multiOptions.active.key = "Active"
site_status.options.multiOptions.active.value = "Active"
site_status.options.multiOptions.active.key = "Inactive"
site_status.options.multiOptions.active.value = "Inactive"
As you can see this is supposed to be a dropdown (select) box, however it is being rendered as a standard text box. What am I doing wrong?
--> Edit
Rather than tying the elements to a form, I am trying to tie them to a database: In my code it would look something like this:
[{tablename}] // the table name would represent a section in the ini
{column}.name = "{column_name/form_field_id}";
{column}.type = "{form_element_type}"
{column}.label = "{form_element_label}"
...
From there I would pull in the database table(s) that the form would represent data for (one or more tables as necessary). As far as the reasoning for this approach is that (down the road), I want to define (either by ini or some other storage method), a configuration file that would be a list of fields/elements that belong to a specific form (that a non-programmer type could easily edit), that the 'generic' form class would read, pull in the element info, and create the form on the fly.
I do realize however this poses another problem which I haven't yet figured out, and that is how to use table lookups for select elements (without coding the database retrieval of the lookup into the form, so that a non-user could easily just define it without any programming, purely configuration, but that is a whole other topic not part of my question here. (and I think I have viable ideas/solutions to that part of the problem anyhow) -- extra config entries and a generic routine pretty much.
I hope that clarifies my thought process and reason why I am doing it the way I am in the example above.
I have not yet played with using a Zend_Config to construct an instance of Zend_Form.
But a look at the code suggests that Zend_Form::addElement() doesn't directly take a Zend_Config instance as a param. Rather, it looks like you need pass your Zend_Config instance to the form constructor. It also seems that the config format needs to be a little deeper in order to map config keys to setXXX() calls.
In path/to/config/myForm.ini:
[myForm]
myForm.elements.site_status.name = "site_status"
myForm.elements.site_status.type = "select"
myForm.elements.site_status.label = "Status"
myForm.elements.site_status.options.multiOptions.active.key = "Active"
myForm.elements.site_status.options.multiOptions.active.value = "Active"
myForm.elements.site_status.options.multiOptions.inactive.key = "Inactive"
myForm.elements.site_status.options.multiOptions.inactive.value = "Inactive"
Then instantiating:
$formConfig = new Zend_Config_Ini('path/to/config/myForm.ini', 'myForm');
$form = new Zend_Form($formConfig);
Not tested, but looking at this example:
Using Zend_Form with Zend_Config - Andrew Vayanis
it feels like it should go something like the above.
Update
In view of the comments/feedback from #Aaron, two more approaches.
We could extend Zend_Form, implementing a method called something like addElementByConfig in which we would pass the shallow Zend_Config instance that describes the element itself. In fact, we could even just override addElement(), taking a recursive approach: if the first param is an instance of Zend_Config, then call addElement() using the component data.
If the atomicity and re-usability are the primary benefits we seek in using Zend_Config to describe an element, then perhaps we just make a custom element extending Zend_Form_Element. Then we could use these elements in any forms we wish.

How do I use add_to in Class::DBI?

I'm trying to use Class::DBI with a simple one parent -> may chidren relationships:
Data::Company->table('Companies');
Data::Company->columns(All => qw/CompanyId Name Url/);
Data::Company->has_many(offers => 'Data::Offer'=>'CompanyId'); # =>'CompanyId'
and
Data::Offer->table('Offers');
Data::Offer->columns(All => qw/OfferId CompanyId MonthlyPrice/);
Data::Offer->has_a(company => 'Data::Company'=>'CompanyId');
I try to add a new record:
my $company = Data::Company->insert({ Name => 'Test', Url => 'http://url' });
my $offer = $company->add_to_offers({ MonthlyPrice => 100 });
But I get:
Can't locate object method "add_to_offers" via package "Data::Company"
I looked at the classical Music::CD example, but I cannot figure out what I am doing wrong.
I agree with Manni, if your package declarations are in the same file, then you need to have the class with the has_a() relationship defined first. Otherwise, if they are in different source files, then the documentation states:
Class::DBI should usually be able to
do the right things, as long as all
classes inherit Class::DBI before
'use'ing any other classes.
As to the three-argument form, you are doing it properly. The third arg for has_many() is the column in the foreign class which is a foreign key to this class. That is, Offer has a CompanyId which points to Company's CompanyId.
Thank you
Well, the issue was actually not my code, but my set up. I realized that this morning after powering on my computer:
* Apache + mod_perl on the server
* SMB mount
When I made changes to several files, not all changes seems to be loaded by mod_perl. Restarting Apache solves the issue. I've actually seen this kind of issue in the past where the client and SMB server's time are out of sync.
The code above works fine with 1 file for each module.
Thank you
I really haven't got much experience with Class:DBI, but I'll give this a shot anyway:
The documentation states that: "the class with the has_a() must be defined earlier than the class with the has_many()".
I cannot find any reference to the way you are using has_a and has_many with three arguments which is always 'CompanyId' in your case.