How to match field of different table in Django - django

I'm using Django as backend, PostgresSQL as DB, and HTML, CSS, and Javascript as frontend. I got stuck to match the field and retrieve specific data in Django.
class Motor(models.Model):
.
.
code = models.CharField(max_length=100)
.
.
class Drum(models.Model):
.
.
code = models.CharField(max_length=100)
.
.
class Cart(models.Model):
.
.
motor = models.ForeignKey(Motor, on_delete=models.CASCADE,null=True, blank=True)
drum = models.ForeignKey(Drum, on_delete=models.CASCADE,null=True, blank=True)
.
.
Now in the Above model, there is the cart Model which saves the data of the Motor Model or else saves the data of the Drum Model or both.
So for example If the user saves the data of the Motor Model in the Cart Model. The data which is saved in the Cart model should match the code field with model Drum and should filter the data accordingly.
So, I have done something like this.
views.py
def Drum(request):
drum_filter = Drum.objects.filter(code__in=Cart.objects.all().values_list('code', flat = True))
return render(request, 'list/drum.html',
{'drum_filter':drum_filter}
)
But now the problem is: The code field in Cart goes as child table and my parent table is Motor. something like this:
[
{
"id": 4,
"quantity": 1,
"user": {
"id": 4,
},
"motor": {
"id": 9,
"name": "...",
"title": "....",
.
.
.
"code": "DD1"
},
"drum": null
}
]
]
Now I unable to match the table field in Django.
So lastly, the code field in the Cart model should match with the code field in the Drum model and if the user does not save any records in the Cart model, then whole Drum model records should filter. Just like this:
Drum = Drum.objects.all().order_by('price')
Last but not least. Is it the right approach to this to filter specific data?

For the first part you could filter as follows
def Drum(request):
drum_filter = Drum.objects.filter(code__in=Cart.objects.values_list('motor__code', flat = True))
return render(request, 'list/drum.html',
{'drum_filter':drum_filter}
)
Second, you can check if cart list is empty
def Drum(request):
carts = Cart.objects.values_list('motor__code', flat = True)
drum_filter = Drum.objects.filter(code__in=carts) if carts else Drum.objects.all().order_by('price')
return render(request, 'list/drum.html',
{'drum_filter':drum_filter}
)
Hope this solves your issue

Related

Django DRF: how to groupby on a foreign fields?

I have a model where users can upvote other users for specific topics. Something like:
#models.py
Class Topic(models.Model):
name = models.StringField()
def __str__(self):
return str(self.name)
Class UserUpvotes(models.Model):
"""Holds total upvotes by user and topic"""
user = models.ForeignKey(User)
topic= models.ForeignKey(Topic)
upvotes = models.PositiveIntegerField(default=0)
Using DRF, I have an API that returns the following: topic_id, topic_name, and upvotes, which is the total upvotes for a given topic.
One of the project requirements is for the API to use these field names specifically: topic_id, topic_name, and upvotes
#serializers.py
class TopicUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.StringRelatedField(source="topic")
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
My trouble is aggregating these fields. I'm filtering the UserUpvotes by user or team and then aggregating by topic.
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_id": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_id": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
At first, I tried creating a TopicSerializer, and then assigning it to the topic field in TopicUpvotesSerializer. But then, the resulting json would have a nested "topic" field and the aggragation would fail.
Attempt 1
#views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
My problem is that the topic_id and topic_name fields are not showing. I get something like:
[
{
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": "12",
"upvotes": 3
},
]
Attempt 2
Another queryset attempt:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
Which yields:
[
{
"upvotes": 14
},
{
"upvotes": 3
},
]
The aggregation worked on the queryset level, but the serializer failed to find the correct fields.
Attempt 3
This was the closest I got:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.values("topic_id", "topic", "upvotes")
.order_by("-upvotes")[:n]
)
[
{
"topic_name": 3,
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "12",
"upvotes": 3
},
]
I have no idea why "topic_name" is simply transforming the "topic_id" into a string, instead of calling the string method.
Work with a serializer for the topic:
class TopicSerializer(serializers.ModelSerializer):
upvotes = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
fields = ['id', 'name', 'upvotes']
then in the ModelViewSet, you annotate:
from django.db.models import Sum
from rest_framework.viewsets import ModelViewSet
class TopicViewSet(ModelViewSet):
serializer_class = TopicSerializer
queryset = Topic.objects.annotate(upvotes=Sum('userupvotes__upvotes'))
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_name": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
The serialized FK will always give you the ID of the related model. I am not sure why you name it topic_name if that is equal to an ID. Now, if you really want to get the name field of the Topic model
in the topic_name = serializers.StringRelatedField(source="topic") you should give it a source="topic.name"
However, if you trying to get the ID of the relation you can still use ModelSerializer :
class TopicUpvotesSerializer(serializers.ModelSerializer):
class Meta:
model = UserUpvotes
fields = "__all__"
#willem-van-onsem's answer is the correct one for the problem as I had put it.
But... I had another use case (sorry! ◑﹏◐), for when the Users API used UserUpvotes serializer as a nested field. So I had to find another solution. This is was I eventually ended up with. I'm posting in case it helps anyone.
class UserUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.SerializerMethodField()
def get_topic_name (self, obj):
try:
_topic_name = obj.topic.name
except TypeError:
_topic_name = obj.get("skill__name", None)
return _topic_name
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
I still have no idea why the SerializerMethodField works and the StringRelatedField field doesn't. It feels like a bug?
Anyways, the rub here is that, after the values().annotate() aggregation, obj is no longer a QuerySet, but a dict. So accessing namedirectly will give you a 'UserUpvotes' object is not subscriptable error.
I don’t know if there are any other edge cases I should be aware of (this is when I REALLY miss type hints in Django), but it works so far

Django: How to retrieve all attributes from related models for GeoJSON serialization?

I have two Models Site and Cell, every site has multiple Cells.
from django.db import models
from django.contrib.gis.db.models import PointField
class SiteManager(models.Model):
def get_by_natural_key(self, name, state_code, location):
return self.get(name=name, state_code=state_code, location=location)
class Site(models.Model):
name = models.CharField(max_length=10)
state_code = models.PositiveSmallIntegerField()
location = PointField()
objects = SiteManager()
class Meta:
unique_together = [['name', 'state_code', 'location']]
def natural_key(self):
return (self.name, self.state_code, self.location)
class Cell(models.Model):
tech = models.CharField(max_length=5)
azimuth = models.IntegerField()
sector_id = models.CharField(max_length=10)
frequency_band = models.CharField(max_length=15)
power = models.DecimalField(decimal_places=2, max_digits=4)
site = models.ForeignKey(Site, on_delete=models.CASCADE)
def natural_key(self):
return (self.tech, self.azimuth,) + self.site.natural_key()
natural_key.dependencies = ['astmaps.site']
I want to retrieve the complete Cell attributes with the related attributes in the Site model, for me to Serialize the resultant Cell's, into GeoJson data, I can easily Serialize the Site model like:
from django.core.serializers import serialize # GeoJSON Serializer
sites = Site.objects.all()
sitesData = serialize('geojson', sites, geometry_field='location',
fields=('name', 'state_code'))
which gives me a GeoJson featureCollection object like:
{
"type":"FeatureCollection",
"crs":{
"type":"name",
"properties":{
"name":"EPSG:4326"
}
},
"features":[
{
"type":"Feature",
"properties": {
"name":"02101",
"state_code":2
},
"geometry":{
"type":"Point",
"coordinates":[
1.34944,
36.1586
]
}
}
]
}
But when It comes to the Cell model, I can't successfully get the geometry field from the related model always null.
Since the Cell model has the Site model as a related model, I've used the function select_related() to get the related attributes:
cells = Cell.objects.select_related('site').all()
cellsData = serialize('geojson', cells, geometry_field='site_location',
fields=('azimuth', ...))
But the GeoJson Serialize function could not identify the Site model attributes from the cells QuerySet:
{
"type":"FeatureCollection",
"crs":{
"type":"name",
"properties":{
"name":"EPSG:4326"
}
},
"features":[
{
"type":"Feature",
"properties":{
"azimuth":340
},
"geometry":null
},
{
"type":"Feature",
"properties":{
"azimuth":340
},
"geometry":null
},
{
"type":"Feature",
"properties":{
"azimuth":240
},
"geometry":null
}
]
}
I've tested the query returned by Django ORM equivalent directly on the database:
cells = Cell.objects.select_related('site').all()
>>> print(cells.query)
SELECT "app_cell"."id", "app_cell"."tech", "app_cell"."azimuth", "app_cell"."sector_id", "app_cell"."frequency_band", "app_cell"."power", "app_cell"."site_id", "app_cell"."id", "app_site"."name", "app_site"."state_code", "app_site"."location"::bytea FROM "app_cell" INNER JOIN "app_site" ON ("app_cell"."site_id" = "app_site"."id")
Which gives me a correct results (all the attributes or columns of the two models):
I've also used Natural Keys, which is the serialization strategy for foreign keys and other relations (as I've read in the documentation and changed the models accordingly):
cellsData = serialize('geojson', cells, geometry_field='site_location',
fields=('azimuth', ...), use_natural_foreign_keys=True)
But the same result, the Serialize method couldn't identify the Site model attributes.
How can I get all the attributes of multiple related models to get serialized using the GeoJSON Serializer?
I managed to get what I want by using raw sql query plus the json_build_object and ST_AsGeoJSON of PostGIS extention:
from django.db import connection
def sites_list(request):
cursor = connection.cursor()
cursor.execute("""select json_build_object(
'type', 'FeatureCollection',
'features', json_agg(ST_AsGeoJSON(t.*)::json)
)
from ( SELECT "app_cell"."id", "app_cell"."tech", "app_cell"."azimuth", "app_cell"."sector_id",
"app_cell"."frequency_band", "app_cell"."power", "app_cell"."site_id", "app_site"."name"
AS "site_name", "app_site"."state_code", "app_cell"."location"::bytea::geometry AS "site_location"
FROM "app_cell" INNER JOIN "app_site" ON ("app_cell"."site_id" = "app_site"."id")
) as t(id, tech, azimuth, sector_id, frequency_band, power, site_id, site_name, state_code, geom);""")
geojson = cursor.fetchone()
return JsonResponse(geojson[0], safe=False)
I had some problems in the JavaScript side, when using:
cursor.fetchall()
return JsonResponse(geojson, safe=False)
the returned GeoJSON object was surrounded by [[]] double brackets, because cursor.fetchall() return a tuple, So, I've used the cursor.fetchone() to get the geoJSON object surrunded only by single brackets [], and used the the index 0 to of the resulted tuble to get the only tuple content as a string, and finally the JsonResponse function will return that result as a JSON object.

Django 1.11 Json Models

I want to create a Django Models Database with an object with entries as such for a checklist inside a report.
JSON File:
{
'int id':{
'description': string,
'value': bool, # yes/no ... represented by true/false
'remarks': string
},
.
.
.
}
such that when I import:
data = filename.json
list ={}
with open('data', 'r') as read_file:
raw_data = json.load(read_file)
i = 0
if i < len(raw_data): # Make sure we don't go out of bounds
# Cycle thru data
for id in raw_data:
s = str(i)
list[i] = {}
list[i]['Description'] = raw_data[s]["Description"]
list[i]['Value'] = raw_data[s]["Value"]
list[i]['Remarks'] = raw_data[s]["Remarks"]
i = i+1
...
...
...
class Report (models.Model):
...
... Other items like reporter's name, etc
...
checklist = list{}
I want to populate this model as such. And I can edit them under the Reports model in the admin page. However i cannot fins what method to use to properly do this. I have tried choice but it’s not what i want.
Edit:
Essentially I want to be able to create those areas for Django Administration to be a part of a
class foo (model.Models):
Example:
For a better resolution:
https://ibb.co/MZcJ2jB
Such that I will have a website where non-admin users can submit reports and be able to make changes to their selection if needed.
please try this code.
data = {"description": "",
"name": "",
"last_name": ""}
model = ""
model += "class Person(models.Model):\n"
for key, value in data.iteritems():
entity = " {} = models.CharField(max_length=30)\n".format(key)
model += entity
model += "\n"
model += "def __str__(self): \n"
model += " return self.name"
print(model)
it will return this.
class Person(models.Model):
last_name = models.CharField(max_length=30)
description = models.CharField(max_length=30)
name = models.CharField(max_length=30)
def __str__(self):
return self.name
after this simply save a .py with the following string as a file.
hope it helps.

Django queryset grouped by count of values in Postgres JSONField

My model:
from django.contrib.postgres.fields import JSONField
class Image(models.Model):
tags = JSONField(null=False, blank=True, default={})
tags field value can be empty, or something like:
[
{"tag": "xxx"},
{"tag": "yyy"},
{"tag": "zzz"}
]
The number or dicts may vary (from 0 to N).
I need to make a query that counts Images grouped by number of tags. Something like:
{
"0": "345",
"1": "1223",
"2": "220",
...
"N": "23"
}
where the key is the number of tags, and the value is the count of Image objects that contains this number of tags.
How can i do that? Thank you for your help!
UPDATE
I modified my code: now I don't use JsonField, but a dedicated model:
class ImageTag(models.Model):
image = models.ForeignKey(Image)
tag = models.CharField()
The question is the same :)

Django - get serialized fields from related model

I can't figure out how to serialize a query that includes fields from a reverse related model. My models look like this. Every vote is linked to a single album:
# models.py
class Album(models.Model):
name = models.CharField(max_length=50)
class Vote(models.Model):
album = models.ForeignKey(Album, on_delete=models.CASCADE)
user_vote = models.BooleanField(default=0)
What I'd like to do is perform a query that returns all Album objects, as well as a sum of the votes attributed to that album. That's easy enough, but when I serialize the query, the "total_votes" field is lost:
# views.py
# this works fine
query = Album.objects.annotate(total_votes = Sum(vote__user_vote))
# after serialization, I lose the field "total_votes"
serialized = serializers.serialize('json', list(query))
return serialized
Unfortunately, the field "total_votes" doesn't appear in the serialized result since, according to Django documentation, "only the fields that are locally defined on the model will be serialized."
So my question is, how do I get the following serialized result (assuming there are 100 votes for Abbey Road and 150 for Astral Weeks)? Any help would be greatly appreciated.
[
{
"pk": 1,
"model": "app.album",
"fields": {
"name": "Abbey Road",
"total_votes": 100
},
{
"pk": 2,
"model": "app.album",
"fields": {
"name": "Astral Weeks",
"total_votes": 150
},
...
]
According to the source, there’s no way to do this using serializers.serialize. The base django serializer will only serialize local_fields on the model instance:
for field in concrete_model._meta.local_fields:
if field.serialize:
if field.remote_field is None:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_field(obj, field)
else:
if self.selected_fields is None or field.attname[:-3] in self.selected_fields:
self.handle_fk_field(obj, field)
for field in concrete_model._meta.many_to_many:
if field.serialize:
if self.selected_fields is None or field.attname in self.selected_fields:
self.handle_m2m_field(obj, field)
In order to get what you want, you’d have to roll your own serialization function (e.g., something that converts your model to a dict and then uses the DjangoJSONEncoder).