Bug when trying to fill Many2many or One2many list in odoo v8 - many-to-many

Since yesterday I'm facing a weird problem.I'm trying to add all the product category list to a contact at creation time.It's not working neither for a one2many/many2one relation or many2many/many2many relation. I always end up with an empty list of categories in the contact.
class product_category(models.Model):
_inherit = "product.category"
contacts = fields.Many2many('res.partner')
class odepoContact(models.Model):
_inherit = "res.partner"
categs = fields.Many2many('product.category')
#api.model
def create(self, values):
## Here categ is a list containing category ids.
categs = self.env['product.category'].search([])
# values['categs'] = (4,0,categs) Not working =>EMPTY
# values['categs'] = categs Not working =>EMPTY
# values['categs'] = (6,0,categs) Not working =>EMPTY
id = super(odepoContact, self).create(values)
_logger.error(id.categs)
return id
LOG:v8dev openerp.addons.org_chart_dept.org_chart: product.category()

Your create() should look like this:
#api.model
def create(self, values):
categs = self.env['product.category'].search([])
values['categs'] = [(6,0,categs.ids)]
id = super(odepoContact, self).create(values)
_logger.error(id.categs)
return id
That code example is for a Many2Many field (categs), which i would prefer here. For Many2Many you need to use a List of tuples. You can find the possible tuples here.

Related

SQLALchemy update ARRAY column [duplicate]

I'm working on a project using Flask and a PostgreSQL database, with SQLAlchemy.
I have Group objects which have a list of User IDs who are members of the group. For some reason, when I try to add an ID to a group, it will not save properly.
If I try members.append(user_id), it doesn't seem to work at all. However, if I try members += [user_id], the id will show up in the view listing all the groups, but if I restart the server, the added value(s) is (are) not there. The initial values, however, are.
Related code:
Adding group to the database initially:
db = SQLAlchemy(app)
# ...
g = Group(request.form['name'], user_id)
db.session.add(g)
db.session.commit()
The Group class:
from flask.ext.sqlalchemy import SQLAlchemy
from sqlalchemy.dialects.postgresql import ARRAY
class Group(db.Model):
__tablename__ = "groups"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
leader = db.Column(db.Integer)
# list of the members in the group based on user id
members = db.Column(ARRAY(db.Integer))
def __init__(self, name, leader):
self.name = name
self.leader = leader
self.members = [leader]
def __repr__(self):
return "Name: {}, Leader: {}, Members: {}".format(self.name, self.leader, self.members)
def add_user(self, user_id):
self.members += [user_id]
My test function for updating the Group:
def add_2_to_group():
g = Group.query.all()[0]
g.add_user(2)
db.session.commit()
return redirect(url_for('show_groups'))
Thanks for any help!
As you have mentioned, the ARRAY datatype in sqlalchemy is immutable. This means it isn’t possible to add new data into array once it has been initialised.
To solve this, create class MutableList.
from sqlalchemy.ext.mutable import Mutable
class MutableList(Mutable, list):
def append(self, value):
list.append(self, value)
self.changed()
#classmethod
def coerce(cls, key, value):
if not isinstance(value, MutableList):
if isinstance(value, list):
return MutableList(value)
return Mutable.coerce(key, value)
else:
return value
This snippet allows you to extend a list to add mutability to it. So, now you can use the class above to create a mutable array type like:
class Group(db.Model):
...
members = db.Column(MutableList.as_mutable(ARRAY(db.Integer)))
...
You can use the flag_modified function to mark the property as having changed. In this example, you could change your add_user method to:
from sqlalchemy.orm.attributes import flag_modified
# ~~~
def add_user(self, user_id):
self.members += [user_id]
flag_modified(self, 'members')
To anyone in the future: so it turns out that arrays through SQLAlchemy are immutable. So, once they're initialized in the database, they can't change size. There's probably a way to do this, but there are better ways to do what we're trying to do.
This is a hacky solution, but what you can do is:
Store the existing array temporarily
Set the column value to None
Set the column value to the existing temporary array
For example:
g = Group.query.all()[0]
temp_array = g.members
g.members = None
db.session.commit()
db.session.refresh(g)
g.members = temp_array
db.session.commit()
In my case it was solved by using the new reference for storing a object variable and assiging that new created variable in object variable.so, Instead of updating the existing objects variable it will create a new reference address which reflect the changes.
Here in Model,
Table: question
optional_id = sa.Column(sa.ARRAY(sa.Integer), nullable=True)
In views,
option_list=list(question.optional_id if question.optional_id else [])
if option_list:
question.optional_id.clear()
option_list.append(obj.id)
question.optional_id=option_list
else:
question.optional_id=[obj.id]

Display on a map objects matching a queryset with Django

I have a Django app, where each user can add a product with multiple possible metrics (width, height and length combination). A user must also specify in which city this product is located.
Users can also search within the database all products matching specific metrics.
I use Django 1.11 and am seaching for a solution to display on an interactive map all the products matching a queryset.
I am trying to do it with django-leaflet and django-geojson (as my db is not gis-oriented and I don't need heavy geo-computations), but I am facing some difficulties because my "PointField" is not in my product Model but in the Location Model and on the map I need to display Product properties, so I must serialize all these data together.
If you prefer code rather than words, here is a simplified version of my relevant files.
#models.py
class Product(models.Model):
name = models.CharField()
owner = models.ForeignKey(User)
photo = models.ImageField(...)
dimensions = models.ManyToManyField(Metrics)
location = models.ForeignKey(Location, related_name='products', related_query_name='product')
class Metrics(models.Model):
width = models.PositiveIntegerField()
height = models.PositiveIntegerField()
length = models.PositiveIntegerField()
class Location(models.Model):
zip_code = models.PositiveIntegerField()
city_name = models.CharField()
slug = models.SlugField(max_length=500, blank=True)
geom = PointField(default={'type': 'Point', 'coordinates': [0, 0]})
#views.py
class SearchResultListView(ListView):
model = models.Product
template_name='my_app/searchresult_list.html'
context_object_name = 'product_list'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
query_width = self.request.GET['width']
query_height = self.request.GET['height']
query_length = self.request.GET['length']
context['product_list'] = context['product_list'].filter(metrics__width=query_width,
metrics__length=query_length, metrics__height=query_height)
return context
#urls.py
????
#template.html
????
I saw in the django-geojson documentation multiple ways to hit the db (GeoJSON layer view,
Tiled GeoJSON layer view, GeoJSON template filter, low-level serialization). But I struggle to find the way to match my needs as my properties are in the Product Model, my coordinates are in the Location Model and my queryset in a non-related class-based view.
Any idea on the best way to perform my task? Should I continue with django-geojson or are there better apps for my purpose?
Your Product and Location look OK, but it is not clear what you are trying to do with Metrics. To select products near some place you want something like:
queryset = Product.objects.filter(location__geom__distance_lt=(someLocation, D(m=50)))
https://docs.djangoproject.com/en/3.0/ref/contrib/gis/geoquerysets/#distance-lt

How to use factory boy to test SQLalchemy association-object models?

I'm using the SQLalchemy association-object pattern (http://docs.sqlalchemy.org/en/rel_1_1/orm/basic_relationships.html#association-object) for three model classes.
Basic relationship is on the left side one User can belong to multiple Organizations. I'm storing extra User-Organization relevant data in the association object class. Then, the association-object class maps a many-to-one to the Organization.
From SQLAlchemy point, the relationship works fine. The problem is testing this with factory boy has proven difficult and always results in error RecursionError: maximum recursion depth exceeded.
Below are the three models for the association object relationship, where User is parent and the Child is Organization:
class MemberOrgsAssoc(Model):
"""The left side of the relationship maps a User as a one-to-many to
Organizations. User-Organization relevant data is stored in
this association-object table. Then, there is a one-to-many from
this association-object table to the Organization table. """
__tablename__ = 'member_orgs'
member_id = Column(db.Integer, db.ForeignKey("users.id"), primary_key=True)
org_id = Column(db.Integer, db.ForeignKey("organizations.id"), primary_key=True)
manager_id = Column(db.Integer, db.ForeignKey("users.id"))
org_title = Column(db.Unicode(50))
organization = relationship("Organization", back_populates="members")
member = relationship("User", back_populates="organizations",
foreign_keys=[member_id])
manager = relationship("User", back_populates="subordinates",
foreign_keys=[manager_id])
class User(SurrogatePK, Model):
"""A user of the app."""
__tablename__ = 'users'
username = Column(db.Unicode(80), unique=True, nullable=False)
organizations = relationship("MemberOrgsAssoc", back_populates="member",
primaryjoin = "member_orgs.c.member_id == User.id",
lazy="dynamic")
subordinates = relationship("MemberOrgsAssoc", back_populates="manager",
primaryjoin = "member_orgs.c.manager_id == User.id",
lazy="dynamic")
class Organization(SurrogatePK, Model):
"""An organization that Users may belong to."""
__tablename__ = 'organizations'
name = Column(db.Unicode(128), nullable=False)
members = relationship("MemberOrgsAssoc", back_populates="organization")
So all the above SQLAlchemy model classes and relationships seem to work as intended for now.
Below are the three factory-boy classes I'm attempting to make work.
MemberOrgs association-object factory:
class MemberOrgsAssocFactory(BaseFactory):
"""Association-object table Factory"""
class Meta:
"""Factory config"""
model = MemberOrgsAssoc
member_id = factory.SubFactory('tests.factories.UserFactory')
org_id = factory.SubFactory('tests.factories.OrganizationFactory')
manager_id = factory.SubFactory('tests.factories.UserFactory')
org_title = Sequence(lambda n: 'CEO{0}'.format(n))
organization = factory.SubFactory('tests.factories.OrganizationFactory')
member = factory.SubFactory('tests.factories.UserFactory')
manager = factory.SubFactory('tests.factories.UserFactory')
class UserFactory(BaseFactory):
"""User factory."""
class Meta:
"""Factory configuration."""
model = User
username = Sequence(lambda n: 'user{0}'.format(n))
organizations = factory.List(
[factory.SubFactory('tests.factories.MemberOrgsAssocFactory')])
subordinates = factory.List(
[factory.SubFactory('tests.factories.MemberOrgsAssocFactory')])
class OrganizationFactory(BaseFactory):
"""Company factory"""
class Meta:
"""Factory config"""
model = Organization
id = Sequence(lambda n: '{0}'.format(n))
name = Sequence(lambda n: 'company{0}'.format(n))
members = factory.List(
[factory.SubFactory('tests.factories.MemberOrgsAssocFactory')])
Finally, need to make a user for the tests and so below is a pytest fixture to make a User. This is where the tests fail due to `RecursionError: maximum recursion depth exceeded".
#pytest.fixture(scope='function')
def user(db):
"""An user for the unit tests.
setup reference: https://github.com/FactoryBoy/factory_boy/issues/101
# how to handle self referential foreign key relation in factory boy
# https://github.com/FactoryBoy/factory_boy/issues/173
"""
user = UserFactory(
organizations__0=None,
subordinates__0=None,
)
a = MemberOrgsAssocFactory(
is_org_admin=True,
is_default_org=True,
is_active=True,
)
a.organization=OrganizationFactory()
user.organizations.append(a)
db.session.commit()
return user
Error message:
E RecursionError: maximum recursion depth exceeded
!!! Recursion detected (same locals & position)
More or less resolved this, though a bit fragile overall. Must follow required pattern carefully as laid out in the sqlalchemy docs:
""" EXAMPLE USE:
# create User object, append an Organization object via association
p = User()
a = MemberOrgsAssoc(extra_data="some data")
a.organization = Organization()
p.organizations.append(a)
# iterate through Organization objects via association, including association attributes:
for assoc in p.organizations:
print(assoc.extra_data)
print(assoc.child)
"""
Below changes to the pytest fixture resolved the RecursionError issue and got it working:
#pytest.fixture(scope='function')
def user(db):
"""An user for the tests."""
user = UserFactory(
organizations='',
subordinates=''
)
a = MemberOrgsAssocFactory(
member_id=None,
org_id=None,
manager_id=None,
is_org_admin=True,
is_default_org=True,
is_active=True,
organization=None,
member=None,
manager=None
)
a.organization = OrganizationFactory(members=[])
user.organizations.append(a)
db.session.commit()
# debugging
# thisuser = User.get_by_id(user.id)
# for assoc in thisuser.organizations:
# if assoc.is_default_org:
# print('The default organization of thisuser is -> {}'.format(assoc.organization.name))
return user

Programmatically identify django foreignkey links

Similar to the question I asked here, if I wanted to list all of the foreign key relationships from a model, is there a way to detect these relationships (forward and backward) automatically?
Specifically, if Model 1 reads
class Mdl_one(models.Model):
name = models.CharField(max_length=30)
and Model 2 reads
class Mdl_two(models.Model):
mdl_one = models.ForeignKey(Mdl_one)
name = models.CharField(max_length=30)
Is there some meta command I can run from Mdl_one (like Model_one()._meta.one_to_many) that tells me that mdl_two has a one-to-many foreign key relationship with it? Simply that mdl_one and mdl_two can be connected, not necessarily that any two objects actually are?
This is you are looking for:
yourModel._meta.get_all_related_objects()
Sample (Edited):
class Alumne(models.Model):
id_alumne = models.AutoField(primary_key=True)
grup = models.ForeignKey(Grup, db_column='id_grup')
nom_alumne = models.CharField("Nom",max_length=240)
cognom1alumne = models.CharField("Cognom1",max_length=240)
cognom2alumne = models.CharField("Cognom2",max_length=240, blank=True)
...
class Expulsio(models.Model): <---!
alumne = models.ForeignKey(Alumne, db_column='id_alumne')
dia_expulsio = models.DateField(blank=True)
...
>>> from alumnes.models import Alumne as A
>>> for x in A._meta.get_all_related_objects():
... print x.name
...
horaris:alumneexclosdelhorari
presencia:controlassitencia
incidencies:entrevista
incidencies:expulsio <---!
incidencies:incidencia
incidencies:incidenciadaula
seguimentTutorial:seguimenttutorial

Inserting data in Many to Many relationship in SQLAlchemy

Suppose I have 3 classes in SQLALchemy: Topic, Tag, Tag_To_Topic.
Is it possible to write something like:
new_topic = Topic("new topic")
Topics.tags = ['tag1', 'tag2', 'tag3']
Which I would like to automatically insert 'tag1', 'tag2' and 'tag3' in Tag table, and also insert the correct relationship between new_topic and these 3 tags in Tag_To_Topic table.
So far I haven't been able to figure out how to do this because of many-to-many relationship. (If it was a one-to-many, it would be very easy, SQLAlchemy would does it by default already. But this is many-to-many.)
Is this possible?
Thanks, Boda Cydo.
Fist of all you could simplify your many-to-many relation by using association_proxy.
Then, I would leave the relation as it is in order not to interfere with what SA does:
# here *tag_to_topic* is the relation Table object
Topic.tags = relation('Tag', secondary=tag_to_topic)
And I suggest that you just create a simple wrapper property that does the job of translating the string list to the relation objects (you probably will rename the relation). Your Tags class would look similar to:
class Topic(Base):
__tablename__ = 'topic'
id = Column(Integer, primary_key=True)
# ... other properties
def _find_or_create_tag(self, tag):
q = Tag.query.filter_by(name=tag)
t = q.first()
if not(t):
t = Tag(tag)
return t
def _get_tags(self):
return [x.name for x in self.tags]
def _set_tags(self, value):
# clear the list first
while self.tags:
del self.tags[0]
# add new tags
for tag in value:
self.tags.append(self._find_or_create_tag(tag))
str_tags = property(_get_tags,
_set_tags,
"Property str_tags is a simple wrapper for tags relation")
Then this code should work:
# Test
o = Topic()
session.add(o)
session.commit()
o.str_tags = ['tag1']
o.str_tags = ['tag1', 'tag4']
session.commit()