How to manage related fields and permissions in Odoo 8? - python-2.7

This question started here: How to manage security with One2many fields in Odoo?. But now, I have simplified the problem and the question is not the same one.
The environment and the problem are the same:
class mother(models.Model):
_name = 'mother'
name = fields.Char(string='Name', size=64, required=True)
is_a_good_mother = fields.Boolean(string='Is a good mother?')
#api.multi
def write(self, vals):
_logger.info('I DO NOT KNOW WHY WHEN CREATING A CHILD THIS ORM '
'METHOD IS BEING EXECUTED, RECEIVING THE KEY '
'is_a_good_mother')
return super(mother, self).write(vals)
class child(models.Model):
_name = 'child'
mother_id = fields.Many2one(comodel_name='mother',
string='Mother', ondelete='cascade')
has_a_good_mother = fields.Boolean(
string='Does the child have a good mother?',
related='mother_id.is_a_good_mother',
related_sudo=True)
I have a menu option which opens a form of Child. This form is auto-generated by Odoo.
The problem
I have an user who can create and modify children, but not mothers. When this user creates the child, a security error raises telling that the user belongs to a group which cannot modify the Mother model. This is due to the line related='mother_id.is_a_good_mother', if I remove it, and the I create a new child, the ORM write method of Mother is not called.
So if B has a related child pointing to any field of A, and you create a new record of B, ORM write method of A is called.
I have a security group my_group, with read 1 create 1 write 1 unlink 1 in B and read 1 create 0 write 0 unlink 0 in A. As an user of this group cannot write A, he gets an error when creating a B record.
How can I avoid this error? I have tried with related_sudo=True, but it did not work, may be I did not use it well.
Can anyone help me?

A related field value is stored in the original field of the "mother" object. So when you try to change it on the "child" object, that's where Odoo updates it behind the scenes. If the user making the change doesn't have a permission to change the "mother" object, an exception will be raised.
You need to make sure that users who don't have permissions to change the target object can't set/change the value of related fields pointing to the object. You can do this for example by making it readonly (readonly=True).

Related

Request changes/additions to data in another app

To make a long story short, I am very grateful for hints on how I can accomplish the following. I have an app A that I don't want to change. I have an app B that needs to select data from A or to request data to be added/changed if necessary. Think of B as an app to suggest data that should end in A only after review/approval. By itself, B is pretty useless. Furthermore, a significant amount of what B's users will enter needs to be rejected. That's why I want A to be protected so to say.
# in app A
class Some_A_Model(models.Model): #e.g., think artist
some_value = models.TextField()
# in app B
class MyCustomField(models.ForeignKey):
...
class Some_B_Model(models.Model): # e.g., think personal preference
best_A = MyCustomField('Some_A_Model')
worst_A = MyCustomField('Some_A_Model')
how_many_times_I_like_the one_better_than_the_other = models.FloatField()
class Mediator(models.Model):
# already exists: generic foreign key
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE
)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(
'content_type',
'object_id'
)
#does not yet exist or needs to be changed:
add_or_change = PickledObjectField()
Django should create a form for Some_B_Model where I can select instances of Some_A_Model for best_A and worst_A, respectively; if, however, my intended best_A is not yet in A's database, I want to be able to request this item to be added. And if I find worst_A is present but has a typo, I want to be able to request this item to be corrected. An editor should be required to review/edit the data entered in B and either reject or release all the associated changes to A's database as an atomic transaction. I don't want any garbage in A and refrain from adding some status field to track what is considered valid, requiring filtering all the time. If it's in A, it must be good.
I figured I need to define a MyCustomField, which could be a customized ForeignKey. In addition, I need some intermediate model ('mediator' maybe?) that MyCustomField would actually be pointing to and that can hold a (generic) ForeignKey to the item I selected, and a pickled instance of the item I would like to see added to A's database (e.g., a pickled, unsaved instance of Some_A_model), or both to request a change. Note that I consider using PickledObjectField from 'django-picklefield', but this is not a must.
As there is only some documentation on custom model fields but not on the further steps regarding form fields and widgets, it seems I have to dig through django's source to find out how to tie my intended functionality into its magic. That's where I am hoping for some comments and hints. Does my plan sound reasonable to you? Is this a known pattern, and if so, what is it called? Maybe someone has already done this or there is a plugin I could look into? What alternatives would you consider?
Many thanks in advance!
Best regards

django model inheritance & FK, could not create unique index

I want to be able to have a foreign key to a parent class, thereby allowing queries of the children classes as well. All other solutions are nightmarish.
I have tried to make this (Destination is also a parent class that has a # of children classes I want to relate to):
class Destination(PolymorphicModel)
class Account(Destination)
class Organization(Destination)
class Person(Destination)
class Transaction(models.Model)
destination = models.ForeignKey(Destination, verbose_name="Destination", null=True, blank=True,
related_name="CompletedTransaction_Destination_FK")
I am referencing destination in other places as well.
This is the error message I get when I try to migrate:
psycopg2.IntegrityError: could not create unique index "baseapp_organization_organization_destination_ptr_id_key"
DETAIL: Key (organization_destination_ptr_id)=(1) is duplicated.
I would love it if I could make the destination class
abstract = True
but then I can't have a foreign key. I need to be able to choose all of those destinations, and they need to remain distinct, real models in the database.
I have also tried GenericRelations, but that proved to be a nightmare as I said earlier.
It feels like I could just get around this error somehow though, any help?
The solution, and I'm guessing it would apply to other errors where the index could not be created, was to wipe the database an delete all the migrations. It was a pain to be sure, but now I can do:
destinations = Destination.objects.all()
and it will give me all the objects, as according to django polymorphic

Manager isn't accessible via (....) instances

I have two tables namely Stuff and Boss. Based on the slug(user_type) from the user, I define which table is going to be used.
def Person_info(request,user_type):
if user_type=="Staff":
item=Staff()
elif user_type=="Boss":
item=Boss()
.................
Then, I need to get the last id for item from its table.
But, I am having "Manager isn't accessible via Staff instances" When I try to get the last id of Staff table.
How can I bypass this problem ?
You are querying using the instance, which is incorrect.
Change your code as below:
def Person_info(request,user_type):
if user_type=="Staff":
# Note no () at the end, which makes the item an instance by instantiating it, not a class by assigning it
item=Staff
elif user_type=="Boss":
item=Boss
....

Assigning values to a query result already set up with a foreign key

I have a database of exhibition listings related by foreign key to a database of venues where they take place. Django templates access the venue information in the query results through listing.venue.name, listing.venue.url, and so on.
However, some exhibitions take place in temporary venues, and that information is stored in the same database, in what would be listing.temp_venue_url and such. Because it seems wasteful and sad to put conditionals all over the templates, I want to move the info for temporary venues to where the templates are expecting info for regular venues. This didn't work:
def transfer_temp_values(listings):
for listing in listings:
if listing.temp_venue:
listing.venue = Venue
listing.venue.name = listing.temp_venue
listing.venue.url = listing.temp_venue_url
listing.venue.state = listing.temp_venue_state
listing.venue.location = listing.temp_venue_location
The error surprised me:
ValueError at /[...]/
Cannot assign "<class 'myproject.gsa.models.Venue'>": "Exhibition.venue" must be a "Venue" instance.
I rather thought it was. How do I go about accomplishing this?
The error message is because you have assigned the class Venue to the listing, rather than an instance of it. You need to call the class to get an instance:
listing.venue = Venue()

Django : Setting a generic (content_type) field with a real object sets it to None

Update 3 (Read This First) :
Yes, this was caused by the object "profile" not having been saved. For those getting the same symptoms, the moral is "If a ForeignKey field seems to be getting set to None when you assign a real object to it, it's probably because that other objects hasn't been saved."
Even if you are 100% sure that it was saved, check again ;-)
Hi,
I'm using content_type / generic foreign keys in a class in Django.
The line to create an instance of the class is roughly this :
tag = SecurityTag(name='name',agent=an_agent,resource=a_resource,interface=an_interface)
Where both agent and resource are content_type fields.
Most of the time, this works as I expect and creates the appropriate object. But I have one specific case where I call this line to create a SecurityTag but the value of the agent field seems to end up as None.
Now, in this particular case, I test, in the preceding line, that the value of an_agent does contain an existing, saved Django.model object of an agent type. And it does.
Nevertheless, the resulting SecurityTag record comes out with None for this field.
I'm quite baffled by this. I'm guessing that somewhere along the line, something is failing in the ORM's attempt to extract the id of the object in an_agent, but there's no error message nor exception being raised. I've checked that the an_agent object is saved and has a value in its id field.
Anyone seen something like this? Or have any ideas?
====
Update : 10 days later exactly the same bug has come to bite me again in a new context :
Here's some code which describes the "security tag" object, which is basically a mapping between
a) some kind of permission-role (known as "agent" in our system) which is a generic content_type,
b) a resource, which is also a generic content_type, (and in the current problem is being given a Pinax "Profile"),
and c) an "interface" (which is basically a type of access ... eg. "Viewable" or "Editable" that is just a string)
class SecurityTag(models.Model) :
name = models.CharField(max_length='50')
agent_content_type = models.ForeignKey(ContentType,related_name='security_tag_agent')
agent_object_id = models.PositiveIntegerField()
agent = generic.GenericForeignKey('agent_content_type', 'agent_object_id')
interface = models.CharField(max_length='50')
resource_content_type = models.ForeignKey(ContentType,related_name='security_tag_resource')
resource_object_id = models.PositiveIntegerField()
resource = generic.GenericForeignKey('resource_content_type', 'resource_object_id')
At a particular moment later, I do this :
print "before %s, %s" % (self.resource,self.agent)
t = SecurityTag(name=self.tag_name,agent=self.agent,resource=self.resource,interface=self.interface_id)
print "after %s, %s, %s, %s" % (t.resource,t.resource_content_type,type(t.resource),t.resource_object_id)
The result of which is that before, the "resource" variable does reference a Profile, but after ...
before phil, TgGroup object
after None, profile, <type 'NoneType'>, None
In other words, while the value of t.resource_content_type has been set to "profile", everything else is None. In my previous encounter with this problem, I "solved" it by reloading the thing I was trying to assign to the generic type. I'm starting to wonder if this is some kind of ORM cache issue ... is the variable "self.resource" holding some kind proxy object rather than the real thing?
One possibility is that the profile hasn't been saved. However, this code is being called as the result of an after_save signal for profile. (It's setting up default permissions), so could it be that the profile save hasn't been committed or something?
Update 2 : following Matthew's suggestion below, I added
print self.resource._get_pk_value() and self.resource.id
which has blown up saying Profile doesn't have _get_pk_value()
So here's what I noticed passing through the Django code: when you create a new instance of a model object via a constructor, a pre-init function called (via signals) for any generic object references.
Rather than directly storing the object you pass in, it stores the type and the primary key.
If your object is persisted and has an ID, this works fine, because when you get the field at a later date, it retrieves it from the database.
However -- if your object doesn't have an ID, the fetch code returns nothing, and the getter returns None!
You can see the code in django.contrib.contenttypes.generic.GenericForeignKey, in the instance_pre_init and __get__ functions.
This doesn't really answer my question or satisfy my curiosity but it does seem to work if I pull the an_agent object out of the database immediately before trying to use it in the SecurityTag constructor.
Previously I was passing a copy that had been made earlier with get_or_create. Did this old instance somehow go out of date or scope?