Django rest api framework ModelSerializer - django

I have ModelSerrializer:
class ProbeParamsSerializer(serializers.ModelSerializer):
modems = ModemParamsSerializer(many=True, required=True)
net_adapters = NetworkAdapterParamsSerializer(many=True, required=True)
class Meta:
model = ProbeParams
fields = (
'probe', 'modems', 'net_adapters', 'ram_free', 'sd_free', 'num_measurement_files', 'num_audio_files',
'cpu_t',
'cpu_load', 'latitude', 'longitude', 'navigation_status', 'info_updated')
I perform:
probe_params_serializer = ProbeParamsSerializer(data=item)
Item look as:
{
"info_updated": 56483476,
"config_updated": 325687,
"ram_free": 800,
"sd_free": 2000,
"num_measurement_files": 8,
"num_audio_files": 2,
"cpu_temp": 38.7,
"cpu_load": 58.4,
"latitude": 0,
"longitude": 0,
"modems": [
Note parameter: cpu_temp. The model is called сpu_t and the report comes under the same name cpu_temp. So naturally when performing validation, I get an error -
"this field (сpu_t) is required".
I understand which is why there is an error, but I do not know how to link these two names. It is necessary to make the value of the parameter cpu_temp are stored in a parameter cpu_t.
Help me please.

Related

Django Rest Framework: Get unique list of values from nested structure

I want to be able to return a list of strings from a deeply nested structure of data. In this scenario, I have a API that manages a chain of bookstores with many locations in different regions.
Currently, I have an API endpoint that takes a region's ID and returns a nested JSON structure of details about the region, the individual bookstores, and the books that can be found in each store.
{
"region": [
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
},
{
"book": {
"name": "Baz"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Bar"
}
}
],
},
{
"store": [
{
"book": {
"name": "Foo"
}
},
{
"book": {
"name": "Baz"
}
},
{
"book": {
"name": "Qux"
}
}
]
}
]
}
My models look like the following. I am aware these models don't make the most sense for this contrived example, but it does reflect my real world code:
class Book(TimeStampedModel):
name = models.CharField(default="", max_length=512)
class Bookstore(TimeStampedModel):
value = models.CharField(default="", max_length=1024)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
class Region(TimeStampedModel):
stores = models.ManyToManyField(Bookstore)
class BookstoreChain(TimeStampedModel):
regions = models.ManyToManyField(Region)
The serializers I created for the above response look like:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class BookstoreSerializer(serializers.ModelSerializer):
books = BookSerializer()
class Meta:
model = Bookstore
fields = "__all__"
class RegionSerializer(serializers.ModelSerializer):
stores = BookstoreSerializer(many=True)
class Meta:
model = Region
fields = "__all__"
class BookstoreChainSerializer(serializers.ModelSerializer):
regions = RegionSerializer(many=True)
class Meta:
model = BookstoreChain
fields = "__all__"
I'm not sure what my view or serializer for this solution need to look like. I'm more familiar with writing raw SQL or using an ORM/Linq to get a set of results.
While the above response is certainty useful, what I really want is an API endpoint to return a unique list of book names that can be found in a given region (Foo, Bar, Baz, Qux). I would hope my response to look like:
{
"books": [
"Foo",
"Bar",
"Baz",
"Qux"
]
}
My feeble attempt so far has a urls.py with the following path:
path("api/regions/<int:pk>/uniqueBooks/", views.UniqueBooksForRegionView.as_view(), name="uniqueBooksForRegion")
My views.py looks like:
class UniqueBooksForRegionView(generics.RetrieveAPIView):
queryset = Regions.objects.all()
serializer_class = ???
So you start from region you have to get the stores, so you can filter the books in the stores, here is a solution which will work.
Note:
Avoid using .get() in *APIView because it will trigger an error if the request does not have the ID, you can use get_object_or_404(), but then you cannot log your error in Sentry.
To get an element from an *APIView, use filter().
import logging as L
class UniqueBooksForRegionView(generics.RetrieveAPIView):
lookup_field = 'pk'
def get(self, *args, **kwargs)
regions = Region.objects.filter(pk=self.kwargs[self.lookup_field])
if regions.exists():
region = regions.first()
stores_qs = region.stores.all()
books_qs = Book.objects.filter(store__in=stores_qs).distinct()
# use your book serializer
serializer = BookSerializer(books_qs, many=True)
return Response(serializer.data, HTTP_200_OK)
else:
L.error(f'Region with id {self.kwargs[self.lookup_field]} not found.')
return Response({'detail':f'Region with id {self.kwargs[self.lookup_field]} not found.'}, HTTP_404_NOT_FOUND)
Note
Here is the flow, the code may need some tweaks, but I hope it helps you understand the flow

Django annotate several same objects in QuerySet by different related object

I got:
# models
class Building(models.Model):
...
class Flat(models.Model):
building = models.ForeignKey(Building)
class Profile(models.Model):
flats = models.ManyToManyField(Flat)
# logic
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile = Profile.objects.create()
profile.flats.add(flat_1)
profile.flats.add(flat_2)
profiles = Profile.objects.filter(flats__building=building)
I got in profiles 2 same profiles. How i can annotate each of them by different flat like this: profiles.first().flat == flat_1 and profiles.last().flat == flat_2?
Maybe Subquery() but how?
UPD I need this in some DRF list view. Output in JSON must be something like:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
To obtain that output, you could do:
data = Profile.objects.all().values('flats', 'id')
return Response(data=data)
in your DRF view.
You don't have to profile instances ...
I wrote the code for your exact needs at the end, but first wrote a couple of things that might be of interest.
In your code sample, you've created only one profile, I'm sure you are not getting 2 instances of Profile that are equals but only one.
The thing is if you have a QuerySet with only one entry, then:
profiles.first() == profiles.last() # True
since profile.first() and profiles.last() are the same instance.
You should try creating 2 Profile instances:
building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)
profile_1 = Profile.objects.create() # You coud/should use bulk_create here.
profile_2 = Profile.objects.create()
profile_1.flats.add(flat_1)
profile_2.flats.add(flat_2)
Then
profiles = Profile.objects.filter(flats__building=building)
will return two different profile objects.
On the other hand, obtaining the JSON like you want ...
Following the example, you posted, filter flats by profile and get the values (this also works if you have more that one profile).
Flat.objects.filter(profile=profile_1).values('profile__id', 'id')
This will return something like ("id" stands for flats ids):
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 1,
"id": 3
}
]
If you do not filter by profile (and you have more than one) you could get something like:
[
{
"profile__id": 1,
"id": 1
},
{
"profile__id": 2,
"id": 3
},
{
"profile__id": 2,
"id": 4
},
...
]
Annotating to get the EXACT json you want:
Filter as shown previously annotate, and get desired values:
Flat.objects.filter(profile=profile_1).annotate(
flat_id=F('id')
).annotate(
profile_id=F('profile__id')
).values(
'profile_id', 'flat_id'
)
will give exactly what you want:
[
{
"profile_id": 1,
"flat_id": 2
},
{
"profile_id": 1,
"flat_id": 3
}
]
You can do that with the right serializer and the right annotation:
The serializer:
class FlatSerializer(serializers.ModelSerializer):
class Meta:
model = Flat
fields = ('flat_id', 'building_id')
flat_id = serializers.CharField(read_only=True)
Then I would simply query Flats rather than profiles and serialize:
flats = Flat.objects \
.annotate(flat_id=F('id')) \
.filter(building=building)
serialized = FlatSerializer(flats, many=True)
print(serialized.data) # [ { flat_id: 1, building_id: 1 }, { flat_id: 2, building_id: 1 } ]
Let me know if that works for you

Django: defining a generic manager in an Abstract model

Using the following code and defining suitable NATURAL_KEY per every class fails (instead of defining different mangers in every class, which replicate the same code):
class NexchangeManager(models.Manager):
def get_by_natural_key(self, param):
if param == "*":
return self.all()
lookup = {self.NATURAL_KEY: param}
return self.get(**lookup)
class NexchangeModel(models.Model):
class Meta:
abstract = True
objects = NexchangeManager()
Djagno complains about fields replication, although NexhcnageModel is an Abstract model.
Should I use a mixin instead?
error:
django.core.serializers.base.DeserializationError: Problem installing fixture '/Users/beoleg/dev/nexchange/core/fixtures/pairs_cross.json': 'NexchangeManager' object has no attribute 'NATURAL_KEY': (core.pair:pk=1) field_value was '['LTC']'
The purpose of this, a bit overcomplicated code at first glance is, to have something like this in my fixtures:
[
{
"model": "payments.paymentpreference",
"pk": 8,
"fields": {
"user": ["onit"],
"identifier": "paypal#nexchange.co.uk",
"payment_method": 12,
"comment": "Please send the funds as a personal payment (this is a precaution to prevent charge backs, payments for goods and services will be automatically declined)",
"currency": [
["*"]
],
"created_on":"2016-11-01T17:41:28+00:00",
"modified_on":"2016-11-01T17:41:28+00:00"
}
}
]
Instaed of:
[
{
"model": "payments.paymentpreference",
"pk": 8,
"fields": {
"user": ["onit"],
"identifier": "paypal#nexchange.co.uk",
"payment_method": 12,
"comment": "Please send the funds as a personal payment (this is a precaution to prevent charge backs, payments for goods and services will be automatically declined)",
"currency": [
["USD"],
["RUB"],
["EUR"],
["GBP"],
["JPY"],
["HRK"],
["CHF"],
["PLN"],
["RON"],
["BGN"],
["CZK"],
["AUD"],
["CAD"],
["NOK"],
["SEK"],
["DKK"],
["HUF"],
["TRY"],
["ZAR"],
["NZD"],
["BRL"],
["IDR"],
["ILS"],
["INR"],
["KRW"],
["MXN"],
["MYR"],
["PHP"],
["THB"]
],
"created_on":"2016-11-01T17:41:28+00:00",
"modified_on":"2016-11-01T17:41:28+00:00"
}
}
]
I don't understand the way you write your manager. If the NATURAL_KEY is an attribute of each model, I would write:
class NexchangeManager(models.Manager):
def get_by_natural_key(self, param):
qs = self.get_queryset()
if param == "*":
return qs.all()
lookup = {qs.model.NATURAL_KEY: param}
return qs.filter(**lookup)
Note: this answer is based on albars answer, but improved for allowing pk params.
manager:
class NexchangeManager(models.Manager):
def get_by_natural_key(self, param):
qs = self.get_queryset()
if param == "*":
return self.all()
lookup = {qs.model.NATURAL_KEY: param}
return self.get(**lookup)
Generic Model class:
class NexchangeModel(models.Model):
class Meta:
abstract = True
objects = NexchangeManager()

how to exclude django hstore field in admin?

The standard approach in admin.py by defining class:
exclude = ('some_hstore_field', )
does not work.
I manage to get expected result by specifying explicit fields but would like rather exclude the one I don't need, than specify all the other needed.
Assuming you have in your model hstore field called facilities:
facilities = hstore.DictionaryField(schema=HSTORE_SCHEMA)
then you CAN NOT just write:
exclude = ('some_non_hstore_field', 'facilities')
Assuming your hstore schema looks something like that:
HSTORE_SCHEMA = [
{
"name": "vegetarian_menu",
"class": "BooleanField",
"kwargs": {
"default": False,
"verbose_name": "vegetarian menu"
}
},
{
"name": "vegan_menu",
"class": "BooleanField",
"kwargs": {
"default": False,
"verbose_name": "vegan menu"
}
}
]
You have to exclude each of subfield by its name, e.g:
exclude = ('some_non_hstore_field', 'vegetarian_menu', 'vegan_menu')
you can do it like this:
exclude = tuple(['some_non_hstore_field'] + [field['name'] for field in HSTORE_SCHEMA])
or like this - using meta of class field:
exclude = tuple(['some_non_hstore_field'] + [field['name'] for field in YourModel._meta.get_field_by_name('facilities')[0].schema])
The same applies for readonly_fields

django serializer with nested column

I'm calling the following serializer -
class ResourceSerializer(serializers.ModelSerializer):
class Meta:
model = Resmst
resource_name = 'resmst'
fields = ('resmst_id', 'resmst_name', 'resmst_desc', 'resmst_limit', 'resmst_inuse', 'resmst_active', 'resmst_lstchgtm',
'resmst_prntid', 'resmst_owner', 'resmst_public', 'resmst_locked', 'resmst_offline')
read_only_fields = ('resmst_id',)
resmst_owner is a FK relation to another table. What I want to do is have the serializer display the information from a column where that FK relates.
This is the current json -
[
{
"resmst_id": 204,
"resmst_name": "GAWK",
"resmst_desc": null,
"resmst_limit": 1,
"resmst_inuse": 0,
"resmst_active": "Y",
"resmst_lstchgtm": "2014-08-20T11:15:18",
"resmst_prntid": null,
"resmst_owner": 822,
"resmst_public": "Y",
"resmst_locked": null,
"resmst_offline": 0
}
]
And this is how I want it to look -
[
{
"resmst_id": 204,
"resmst_name": "GAWK",
"resmst_desc": null,
"resmst_limit": 1,
"resmst_inuse": 0,
"resmst_active": "Y",
"resmst_lstchgtm": "2014-08-20T11:15:18",
"resmst_prntid": null,
"owner_name": "John Smith",
"resmst_public": "Y",
"resmst_locked": null,
"resmst_offline": 0
}
]
Or am I stuck with having to do it this way -
[
{
"resmst_id": 204,
"resmst_name": "GAWK",
"resmst_desc": null,
"resmst_limit": 1,
"resmst_inuse": 0,
"resmst_active": "Y",
"resmst_lstchgtm": "2014-08-20T11:15:18",
"resmst_prntid": null,
"resmst_owner": {
"owner_name": "John Smith"
},
"resmst_public": "Y",
"resmst_locked": null,
"resmst_offline": 0
}
]
Haven't tested this but it looks like this question. Try to use a SerializerMethodField.
I think you should be fine with a regular Field (note that this is readonly).
class ResourceSerializer(serializers.ModelSerializer):
owner_name = serializers.Field(source='resmst_owner.owner_name')
class Meta:
model = Resmst
resource_name = 'resmst'
fields = ('resmst_id', 'resmst_name', 'resmst_desc', 'resmst_limit',
'resmst_inuse', 'resmst_active', 'resmst_lstchgtm',
'resmst_prntid', 'resmst_owner', 'resmst_public',
'resmst_locked', 'resmst_offline', 'owner_name', )
read_only_fields = ('resmst_id',)
Don't forget to add it to Meta.fields (as above).
See the section on Core arguments:
source
The name of the attribute that will be used to populate the
field. May be a method that only takes a self argument, such as
Field(source='get_absolute_url'), or may use dotted notation to
traverse attributes, such as Field(source='user.email').
The value source='*' has a special meaning, and is used to indicate
that the entire object should be passed through to the field. This can
be useful for creating nested representations. (See the implementation
of the PaginationSerializer class for an example.)
Defaults to the name of the field.