Use order output parameter and specify serialization key with marshmallow-sqlalchemy - marshmallow-sqlalchemy

I'm using marshmallow 3.4.0 and marshmallow-sqlalchemy 0.22.2
I have this SQLAlchemy model and Marshmallow schema
class Zone(Base):
zone_id = Column(Integer, primary_key=True)
name = Column(String(65))
location_id = Column(Integer, ForeignKey('locations.location_id'))
location = relationship("Location", lazy="joined")
class ZoneSchema(SQLAlchemySchema):
class Meta:
model = Zone
fields = ("zone_id", "name", "location")
ordered = True
load_instance = True
zone_id = auto_field(data_key="id")
location = fields.Nested(LocationSchema)
If I remove
zone_id = auto_field(data_key="id")
the fields are ordered as requested.
If I remove
fields = ("zone_id", "name", "location")
ordered = True
The key is set to id as requested.
If I run the code above, I get this error
File "[...]/models_schema.py", line 30, in <module>
class ZoneSchema(SQLAlchemySchema):
File "/venvs/backend-api/lib/python3.8/site-packages/marshmallow/schema.py", line 107, in __new__
cls_fields = _get_fields(attrs, base.FieldABC, pop=True, ordered=ordered)
File "/venvs/backend-api/lib/python3.8/site-packages/marshmallow/schema.py", line 57, in _get_fields
fields.sort(key=lambda pair: pair[1]._creation_index)
File "/venvs/backend-api/lib/python3.8/site-packages/marshmallow/schema.py", line 57, in <lambda>
fields.sort(key=lambda pair: pair[1]._creation_index)
AttributeError: 'SQLAlchemyAutoField' object has no attribute '_creation_index'
Could someone explain to me how I can set access the zone_id field and add the "data_key" property ?

As a last resort, I read the documentation and I managed to find an answer by myself.
class ZoneSchema(SQLAlchemySchema):
class Meta:
model = Zone
fields = ("zone_id", "name", "location")
ordered = True
load_instance = True
location = fields.Nested(LocationSchema)
#pre_dump
def set_data_key_for_zone_id(self, data, many, **kwargs):
self.fields.get("zone_id").data_key = "id"
return data
As you can see, I kept the ordered = True part and use the pre-dump decorator to access the zone_id field and set the data_key attribute.
If there is a better way to do this, please let me/us know !

Related

GDALException OGR failure

I am working with Django DRF and GeoDjango for a simple model which is as follows.
class Company(models.Model):
name = models.CharField(max_length=200, default='Company', null=True)
def __unicode__(self):
return self.name
class Shop(models.Model):
name = models.CharField(max_length=200, default="bla")
address = models.CharField(max_length=300, default='blabla')
location = models.PointField(null=True, blank=True, geography=True)
company = models.ForeignKey(
Company, on_delete=models.CASCADE, null=True)
This is its serializer.py
class ShopSerializer(serializers.ModelSerializer):
distance = serializers.DecimalField(
source='distance.km', max_digits=10, decimal_places=2, required=False, read_only=True)
class Meta:
model = Shop
fields = ['id', 'name', 'address', 'location', 'distance']
# read_only_fields = ['distance']
class CompanySerializer(serializers.ModelSerializer):
shop_set = ShopSerializer(many=True)
class Meta:
model = Company
fields = ['id', 'name', 'shop_set']
def create(self, validated_data):
shop_validated_data = validated_data.pop('shop_set')
company = Company.objects.create(**validated_data)
shop_set_serializer = self.fields['shop_set']
for each in shop_validated_data:
each['company'] = company
shops = shop_set_serializer.create(shop_validated_data)
return company
everything works fine until I add rest_framework_gis in my settings.py file or add the following line in my shop serializer
serialize('geojson', Shop.objects.all(),
geometry_field='location', fields=('name', 'address'))
In both the cases get GDALException OGR failure.
I have checked my versions of GDAL and Python. Both are 64 bit. And both python and GDAL are working fine.
What I basically need to do here is to convert my POINT field into json lat long response right now the response is as such (if i do not include the lines that cause error).
{
"id": 1,
"name": "Cosmetica",
"address": "somwhere",
"location": "SRID=4326;POINT (24.896 67.182)"
}
Please help.

How to write fields many2one in Odoo with XMLRPC

I want to use the xmlrpc for the model "product.category".
How to write the field "categ_id" with "read and search"in PYTHON ? I have always an error. I need to do that because i have to transfer this informations into another database Odoo.
import xmlrpc.client
import time
url_db1="http://localhost:8069"
db_1='DB_ONE'
username_db_1='username'
password_db_1='password'
common_1 = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url_db1))
models_1 = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url_db1))
version_db1 = common_1.version()
print("details..", version_db1)
url_db2="http://localhost:806972"
db_2='DB_2'
username_db_2='myemail'
password_db_2='mypassword'
common_2 = xmlrpc.client.ServerProxy('{}/xmlrpc/2/common'.format(url_db2))
models_2 = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url_db2))
version_db2 = common_2.version()
print("details..", version_db2)
uid_db1 = common_1.authenticate(db_1, username_db_1, password_db_1, {})
uid_db2 = common_2.authenticate(db_2, username_db_2, password_db_2, {})
db_1_categories = models_1.execute_kw(db_1, uid_db1, password_db_1, 'product.category', 'search_read', [[]], {'fields':['id','name', 'parent_id']})
total_count = 0
for category in db_1_categories:
print("category..", category)
total_count +=1
values = {}
values['id'] = category['id']
values['name'] = category['name']
if category['parent_id']:
values['parent_id'] = category['parent_id'][0]
new_lead = models_2.execute_kw(db_2, uid_db2, password_db_2, 'product.category', 'create', [values])
print("Total Created..", total_count)
This is the error :
ValidationError: ('The operation cannot be completed: another model requires the record being deleted. If possible, archive it instead.\n\nModel: Product Category (product.category), Constraint: product_category_parent_id_fkey', None)\n'>
The method name should be search_read
Example:
models.execute_kw(db, uid, password,
'product.template', 'search_read',
[],
{'fields': ['name', 'default_code', 'categ_id']})
It should return a list of dicts, the categ_id field value is a list of two values, the first is the id of the category and the second is the name of the category.
To write categ_id, you just need to provide the category ID to the write method.
Example:
product_template_data = [{'default_code': 'FURN_6666', 'categ_id': [8, 'All / Saleable / Office Furniture'], 'id': 23, 'name': 'Acoustic Bloc Screens'}, ...]
for ptd in product_template_data:
models.execute_kw(db, uid, password, 'product.template', 'write',
[[ptd['id']],
{
'categ_id': ptd['categ_id'][0],
...
}
])
You mentioned that you need to transfer data to another database, the product template is probably not present which means that you can't call the write method instead, you can call the create method.
Example:
id = models.execute_kw(db, uid, password, 'product.template', 'create', [{
'categ_id': ptd['categ_id'][0],
...
}])
Edit:
You will get an invalid syntax error using:
[product,'categ_id': product['categ_id'][0],]
To pass values to the create method, you need to pass args to the execute_kw method as a list then pass values as a dictionary inside that list.
Edit:
values = {}
values['name'] = product['name']
values['categ_id'] = product['categ_id'][0]
...
new_lead = models_2.execute_kw(db_2, uid_db2, password_db_2, 'product.template', 'create', [values])
Edit: Use the parent category id in the new database
When we call the create method it will create a new record and return its ID which is probably different the one passed through the values dictionary.
To avoid the ValidationError you can use a dictionary where the parent ID in the old database is the key and the new ID is the value then you have just to pass that value when creating a new category.
category_ids = {}
for category in db_1_categories:
print("category..", category)
total_count +=1
values = {}
# values['id'] = category['id']
values['name'] = category['name']
if category['parent_id']:
values['parent_id'] = category_ids[category['parent_id'][0]]
category_id = models_2.execute_kw(db_2, uid_db2, password_db_2, 'product.category', 'create', [values])
category_ids[category['id']] = category_id
first of all error in your code
This is your code
db_1_products = models_1.execute_kw(db_1, uid_db1, password_db_1, 'product.template', 'search_read', [[]], {'fields':['id','name', 'categ_id','type', 'default_code', 'list_price', 'website_url', 'inventory_availibility', 'website_description']})
total_count = 0
for product in db_1_products:
print("produt..", product)
total_count +=1
new_lead = models_2.execute_kw(db_2, uid_db2, password_db_2, 'product.template', 'create', [product,'categ_id': product['categ_id'][0],])
print("Total Created..", total_count)
This is updated code
db_1_products = models_1.execute_kw(db_1, uid_db1, password_db_1, 'product.template', 'search_read', [[]], {'fields':['id','name', 'categ_id','type', 'default_code', 'list_price', 'website_url', 'inventory_availibility', 'website_description']})
total_count = 0
for product in db_1_products:
print("produt..", product)
total_count +=1
new_lead = models_2.execute_kw(db_2, uid_db2, password_db_2, 'product.template', 'create', [{product,'categ_id': product['categ_id'][0]}])
print("Total Created..", total_count)
you need to pass dictionary when creating of any model in odoo.

Rails Import: "No Implicit conversion of string into array"

I'm attempting to import values from a spreadsheet into a table. I am adding new records for each row to an array, and then importing that array. My Code is below:
def import(path)
spreadsheet = Roo::Spreadsheet.open(path+"Data.xlsx")
sales = spreadsheet.sheet('Accounts')
sales = sales.parse(headers: true)
accounts = []
sales.each do |row|
a = HM::NewBusiness.new
a.dealer_id = row["Dlr #"]
a.dealer_name = row["Dealer Name"]
a.duns = row["Duns Name"]
a.industry = row["Type"]
a.volume_2016 = row["volume_2016"]
a.volume_2017 = row["volume_2017"]
a.volume_2018 = row["volume_2018"]
a.volume_2019 = row["volume_2019"]
accounts << a
end
pp accounts
HM::NewBusiness.import(accounts)
end
However when I run import, I get:
TypeError: no implicit conversion of String into Array
I can't figure out where I'm going wrong. Any help would be appreciated.
Figured it out. The problem was that I coincidentally named the method itself "import". In short a 1d10t error.

find_or_create_by ignore if column not nil

I have a rake file setup to import JSON data from an external URL. In this JSON there is a field titled 'deleted'. This field is a BIGINT, and if the field has been deleted then it will fill with random BIGINT.
I want to import all rows except those that have the 'deleted' field populated.
This is what I have so far, which works well except importing all rows.
data_json['Agent'].each do |data|
agent = Agent.find_or_create_by(agent_id: data['id'])
agent. agent_id = data['id']
agent.first_name = data['first_name']
agent.last_name = data['last_name']
agent.deleted = data['deleted']
agent.save
end
I believe what you're looking for it the next keyword. You could try something like this:
data_json['Agent'].each do |data|
agent = Agent.find_or_create_by(agent_id: data['id'])
agent. agent_id = data['id']
agent.first_name = data['first_name']
agent.last_name = data['last_name']
agent.deleted = !data['deleted'].nil? # Force this into a bool
next if agent.deleted?
agent.save
end

Django Admin: Many-to-Many listbox doesn't show up with a through parameter

I have the following models:
class Message(models.Model):
date = models.DateTimeField()
user = models.ForeignKey(User)
thread = models.ForeignKey('self', blank=True, null=True)
...
class Forum(models.Model):
name = models.CharField(max_length=24)
messages = models.ManyToManyField(Message, through="Message_forum", blank=True, null=True)
...
class Message_forum(models.Model):
message = models.ForeignKey(Message)
forum = models.ForeignKey(Forum)
status = models.IntegerField()
position = models.IntegerField(blank=True, null=True)
tags = models.ManyToManyField(Tag, blank=True, null=True)
In the admin site, when I go to add/change a forum, I don't see the messages listbox as you'd expect. However, it shows up if I remove the 'through' parameter in the ManyToManyField declaration. What's up with that? I've registered all three models (plus Tag) to the admin site in admin.py.
TIA
Documentation says:
When you specify an intermediary model using the through argument to a
ManyToManyField, the admin will not display a widget by default.
But it's probably possible to display M2M fields in the admin change view even if the through attribute is defined.
class ForumAdminForm(forms.ModelForm):
mm = forms.ModelMultipleChoiceField(
queryset=models.Message.objects.all(),
widget=FilteredSelectMultiple(_('ss'), False, attrs={'rows':'10'}))
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
initial = kwargs.setdefault('initial', {})
initial['mm'] = [t.service.pk for t in kwargs['instance'].message_forum_set.all()]
forms.ModelForm.__init__(self, *args, **kwargs)
def save(self, commit=True):
instance = forms.ModelForm.save(self, commit)
old_save_m2m = self.save_m2m
def save_m2m():
old_save_m2m()
messages = [s for s in self.cleaned_data['ss']]
for mf in instance.message_forum_set.all():
if mf.service not in messages:
mf.delete()
else:
messages.remove(mf.service)
for message in messages:
Message_forum.objects.create(message=message, forum=instance)
self.save_m2m = save_m2m
return instance
class Meta:
model = models.Forum
class ForumAdmin(admin.ModelAdmin):
form = ForumAdminForm
Take a look at the official documentation:
I learned a lot from #Fedor's answer, but some comments and cleanup may be still beneficial.
class ForumAdminForm(forms.ModelForm):
messages = forms.ModelMultipleChoiceField(
queryset=Message.objects.all(),
widget=FilteredSelectMultiple('Message', False))
# Technically, you don't need to manually set initial here for ForumAdminForm
# However, you NEED to do the following for MessageAdminForm
def __init__(self, *args, **kwargs):
if 'instance' in kwargs:
# a record is being changed. building initial
initial = kwargs.setdefault('initial', {})
initial['messages'] = [t.message.pk for t in kwargs['instance'].message_forum_set.all()]
super(ForumAdminForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
if not self.is_valid():
raise HttpResponseForbidden
instance = super(ForumAdminForm, self).save(self, commit)
def save_m2m_with_through():
messages = [t for t in self.cleaned_data['messages']
old_memberships = instance.message_forum_set.all()
for old in old_memberships:
if old.message not in messages:
# and old membership is cleaned by the user
old.delete()
for message in [x for x in messages not in map(lambda x: x.message, old_memberships)]:
membership = Member_forum(message=messsage, forum=instance)
# You may have to initialize status, position and tag for your need
membership.save()
if commit:
save_m2m_with_through()
else:
self.save_m2m = save_m2m_with_through
return instance
class Meta:
model = Forum
fields = {'name', 'messages')
There's one caveat: if you have another many-to-many relationship in the models (that is without through), super(ForumAdminForm, self).save(self, commit) will set self.save_m2m in case commit is False. However, calling this would cause an error, because this function also tries to save the many-to-many with through as well. You may need to save all other many-to-many relationship manually, or catch the exception, or else.
Django admin nicely support many-to-many intermediary models that using the through argument .
For example you have these Person and Group models with intermediate Membership model:
models.py
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Now in admin.py file ,
Define an inline class for the intermediate Membership model:
#admin.register(Membership)
class MembershipInline(admin.TabularInline):
model = Membership
extra = 1
And use them in admin views of models:
#admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
#admin.register(Group)
class GroupAdmin(admin.ModelAdmin):
inlines = (MembershipInline,)
More info in official docs:
Models,
Admin

Resources