Django Rest Framework: Why does PrimaryKeyRelatedField document as a string in the schema when read_only? - python-3.x

I have a serializer with a PrimaryKeyRelatedField:
field_name = serializers.PrimaryKeyRelatedField(queryset=ModelClass.objects.all(), read_only=False)
With this setup, the Schema properly identifies the parameter as an integer (the PK). But, when I change to:
field_name = serializers.PrimaryKeyRelatedField(read_only=True)
(it will not let you specify queryset and read_only at the same time) then the parameter is identified as a string in the Schema.
Why would this be? Is this correct/expected behavior or perhaps a bug?

Well, I had banged my head on this one long enough to post on SO, but as I've continued to dig into it I think I am making some progress.
At rest_framework/schemas/openapi.py:380 inside of map_field() you find the following:
if isinstance(field, serializers.PrimaryKeyRelatedField):
model = getattr(field.queryset, 'model', None)
if model is not None:
model_field = model._meta.pk
if isinstance(model_field, models.AutoField):
return {'type': 'integer'}
RelatedField won't let you specify a queryset when read_only=True though, so then the "model" in the above code snippet is always None when read_only=True, and you end up getting type="string" at the bottom of the map_field function.
So this explains why it is currently happening, and seems like a bug in DRF, but now I guess I just need to look into what the appropriate fix would be.
I've opened up a corresponding issue against DRF: https://github.com/encode/django-rest-framework/issues/7427

Related

adding reserved lot name in sale.order.line in odoov10

I would like to write small module that inherit sale.order.line and adding reserved lot on sale order line tree view.
under the sale.order.line module, there is the field name move_ids (stock.move , one2many) fields. I would like to create move_ids.move_line_ids.lot_id
So I tried the following code:
lot_name = fields.Char(related="move_ids.move_line_ids.lot_id", string="String")
but no luck and saw internal server error.
jooze
You can not do that, type of related field lot_name is inconsistent with lot_id.
Try to use a computed Many2many field to get all lot ids related to the current record.
Edit:
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
lot_ids = fields.Many2many('stock.production.lot', compute='_get_lot_ids')
#api.one
#api.depends('move_ids.move_line_ids.lot_id')
def _get_lot_ids(self):
# returns the union of all lots, with duplicates removed
self.lot_ids = self.mapped('move_ids.move_line_ids.lot_id')

How to convert a Hit into a Document with elasticsearch-dsl?

Consider the following mapping for a document in ES.
class MyDoc(elasticseach_dsl.Document):
id_info = Object(IdInfo)
class IdInfo(elasticseach_dsl.InnerDoc):
id = Keyword()
type = Keyword()
Using elasticsearch-dsl, there are 2 ways of retrieving a document (that I am interested in):
Using MyDoc.search().query().execute(), that yields Hit objects
Using MyDoc.get(), that yields a MyDoc object
Here is the issue I am experiencing:
When I retrieve the same document from ES, and that document is missing, for example, the type field, I get different behaviours:
When using search(): doc being a Hit object, accessing doc.type raises a KeyError
When using get(): doc being a MyDoc object, accessing doc.type simply returns None
To workaround this discrepancy, I would like to convert a Hit instance to a MyDoc instance, so that I can always use the doc.type syntax without any errors being raised.
How can I do that?
Alternatively, is there a way that I could access Hit instances with the same behaviour as MyDoc instances?
dict_hit = hit.to_dict()
doc = YourDocument(**dict_hit)
doc.property1 # you can access the property here
I know it is a bit awkward and annoying, it used to work with versions below 6.
I found a workaround, if you take the dictionary coming out from elasticsearch response you can then ask the document class to interpret it like the following.
query = MyDoc.search()
response = query.execute()
my_doc = MyDoc.from_es(response.hits.hits[0])
We were facing this situation. In our case, is was due to the index name in the Index subclass to configure Document indices. Our model looked more or les like this:
class MyDoc(Document):
my_field = Keyword()
class Index:
name = "my-doc-v1-*"
This way, when querying for documents in indexes that match that name (for example "my-doc-v1-2022-07"), hits are automatically instantianted as MyDoc objects.
Now we have started to generate 'v2' indices, named like "my-doc-v2--000001", and then hits were not being populated as MyDoc objects.
For that to happen, we had to change Index.name to my-doc-*. That way, documents from both 'v1' and 'v2' indices are always populated automatically by the library, since they match the Index.name expression.

Flask-AppBuilder equivalent of SQLite WHERE clause to filter column data

I'm new to Flask and have started designing a front end for an inventory management database using Flask-AppBuilder.
I have created several models and have have managed to display my sqlite data in tables using Flask-AppBuilder's views.
However, I don't seem to be able to find the equivalent of SQLite WHERE clause to filter or "restrict" column data. I've been reading a lot about sqlalchemy, filters, queries but this has left me more confused that anything else and the explanations seem to be extremely elaborate and complicated to do something which is extremely simple.
Assuming we reproduce the following SQLite query in Flask-AppBuilder:
SELECT Field_A
FROM Table_A
WHERE Field_A = 'some text'
with:
result = session.query(Table_A).filter_by(Field_A = 'some text').all()
Where does the above line of code go in my app?
Considering I have the following Class:
class Table_A(Model):
id = Column(Integer, primary_key=True)
Field_A = Column(String)
def __repr__(self):
return self
and View:
class Table_AView(ModelView):
datamodel = SQLAInterface(Table_AView)
label_columns = {'Field_A':'A'}
list_columns = ['Field_A']
After much digging flask-appbuilder uses it's own filterclass in order to enable you to filter your views.
All the classes are referenced here on GitHub:
Flask Filter Clases List
Also not the difference between FilterEqual and FilterEqualFunction here:
What is the difference between : FilterEqual and FilterEqualFunction?
For other customisation and first port of call of Flask-appbuilder go straight to the API Reference where you'll find a couple of examples of the filterclass in action.
In essence it is extremely simple. In your views.py code within the ModelView class you want to filter simply add base_filters = [['field_A', FilterEqual, 'abc']] like so:
`class Table_AView(ModelView):
datamodel = SQLAInterface(Table_AView)
label_columns = {'Field_A':'A'}
list_columns = ['Field_A']
base_filters = [['field_A', FilterEqual, 'abc']]`
This will only show the lines where the field_A variable is equal to abc.
Hope this helps someone as it took me nearly (sigh) two weeks to figure it out...
SQLALchemy is an ORM (Object-Relational Mapping), it mean that you dont have to deal with raw SQL, you will call a function that you "build" (by adding filters in your case). It will transparently generate an SQL query, execute it, and return the result as python objects.
I would suggest you to read closely at sqlalchemy documentation about filters again, especially filter_by :
http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.filter_by
It is the easiest way to apply a WHERE with sqlalchemy.
If you have declared correctly the model for Table_A, you should be able to use it so:
result = session.query(Table_A).filter_by(Field_A = 'some text').all()
Here session.query(Table_A).filter_by(Field_A = 'some text') will generate the SQL, and .all() will execute it.

How to capture many to many field values via get_initial command

I am slowly progressing in my django journey, but this one has me stumped. I am trying to populate a CreateView with a different model via a copy command using the get_initial override. All of the attributes copy as I would expect with the exception of the ManytoMany fields. I've researched this topic most of today, and found the following which is very close to what I'm trying to figure out KeyError: 'manager' in django get_initial.
My View...
class BookView(LoginRequiredMixin,CreateView):
model = Book
template_name = 'book/titles.html'
form_class = BookForm
def get_initial(self):
initial = super(BookView, self).get_initial()
author = author.objects.get(pk=self.kwargs["pk"])
initial = author.__dict__.copy()
initial.update({
"author": author.name,
}}
for field in self.form_class.base_fields.items():
value = getattr(self.get_object(), field)
if field == 'author':
value = self.get_object().author.all()
initial.update({field: value})
return initial
I incorporated the suggested change based on the issue that I found on SO, but I still am getting a 'manager" KeyError. I am ultimately trying to populate the manytomanyfield in my model and then save the values, but to no avail. Any suggests are appreciated!
What a difference a day makes....
def get_initial(self):
initial = super(BookView, self).get_initial()
author = author.objects.get(pk=self.kwargs["pk"])
initial = author.__dict__.copy()
initial.update({
"author": author.name.all(),
}}
return initial
I added a .all() after the reference to the manytomanyfield in my initial get and also update the form to get the field in question. Much cleaner than a few hacks I kinda got working along the way.

Django Haystack faceting on the model type

I want to facet the results based on the different model_names (classes) returned. Is there an easy way to do this?
Have you tried adding a SearchIndex field with this information? E.g.
class NoteIndex(SearchIndex, indexes.Indexable):
title = CharField(model_attr='title')
facet_model_name = CharField(faceted=True)
def get_model(self):
return Note
def prepare_facet_model_name(self, obj):
return "note"
class MemoIndex(SearchIndex, indexes.Indexable):
title = CharField(model_attr='title')
facet_model_name = CharField(faceted=True)
def get_model(self):
return Memo
def prepare_facet_model_name(self, obj):
return "memo"
And so on, simply returning a different string for each search index. You could also create a mixin and return the name of the model returned by get_model too.
Presuming you've added this field to each of your SearchIndex definitions, just chain the facet method to your results.
results = form.search().facet('facet_model_name')
Now the facet_counts method will return a dictionary with the faceted fields and count of results for each facet value, in this case, the model names.
Note that the field here is labeled verbosely to avoid a possible conflict with model_name, a field added by Haystack. It's not faceted, and I'm not sure if duplicating it will cause a conflict.
If you just want to filter on the model type, you can use the ModelSearchForm
The Docs have a really good walk-through for this.
The minimum you'll need:
is to add faceted=True to the params of your model_names field.
Rebuild your schema and indices.
add .facet('model_names') to whatever SearchQuerySet you're wanting to facet.
More explanation on the question would enable a more complete answer.

Resources