iteration through lookups table in Django Rest Framework - python

I have a lookups table that contains course categories and subcategories separate:
{
"id": 138,
"lookup": "CRS_CTGRY",
"attr1": "Arts and Humanities",
"attr2": "الفنون والعلوم الإنسانية",
"attr3": null,
"attr4": null,
"attr5": null,
"editable": 1
},
{
"id": 155,
"lookup": "CRS_SB_CTGRY",
"attr1": "Photography",
"attr2": "النصوير",
"attr3": "138",
"attr4": null,
"attr5": null,
"editable": 1
},
The relation between them is that attr3 = id_of_the_category && attr1 = CRS_SB_CTGRY
I want to merge them together in one list like:
{"id":138,"
"lookup":"CRS_CTRGY",
"name":"Arts and Humanities",
"subcategories":{"id": 154,
"lookup": "CRS_SB_CTGRY",
"attr1": "Music",
"attr2": "الموسيقي",
"attr3": "138",
"attr4": null,
"attr5": null,
"editable": 1
}}
This is my models.py:
class Lookups(models.Model):
lookup = models.CharField(max_length=45)
attr1 = models.CharField(max_length=100)
attr2 = models.CharField(max_length=100, blank=True, null=True)
attr3 = models.CharField(max_length=100, blank=True, null=True)
attr4 = models.CharField(max_length=100, blank=True, null=True)
attr5 = models.CharField(max_length=100, blank=True, null=True)
editable = models.IntegerField(blank=True, null=True)
class Meta:
managed = True
db_table = 'lookups'
unique_together = (('lookup', 'attr1', 'attr2', 'attr3', 'attr4', 'attr5'),)
How can i do it? and where to put the code? in the serializers class?

I really have no idea what you are trying to do, because your example (text) doesn't match the example (code screenshots).
From your code screenshot it seems that if lookup is CRS_CTGRY, this is the main category, and if lookup is CRS_SUB_CTGRY, then this is a sub category, and the parent category id is stored in attr3.
Now you want you get a dictionary object for this structure. Assuming you have all the data in your model:
combined = []
lookup_indices = {} # stores parents lookup for easier access.
for obj in Lookups.objects.filter(lookup='CRS_CTGY').values():
obj['subcategories'] = []
combined.append(obj)
lookup_indicies[obj.pk] = len(combined) - 1
for obj in Lookups.objects.filter(lookup='CRS_SUB_CTGRY').values():
try:
combined[lookup_indicies[int(obj['attr3'])]]['subcategories'].append(obj)
except KeyError:
print('No parent id {} found for sub cat {}'.format(obj['attr3'], obj))
print(combined)

Related

How to display properties of a nested object in Django Rest Framework

I am using Django and Django-Rest-Framework to build an API for a battle system. In my code, I have 2 models: A parent model Battle and a child model Round. Round has some #property fields (start_time, end_time, score) which are calculated based on different values. When I access the Round route directly, I get the desired output:
http://127.0.0.1:8001/battle/rounds/1/
{
"id": 1,
"battle": "http://127.0.0.1:8001/battle/battles/1/",
"index": 0,
"contender_entry": null,
"opponent_entry": null,
"start_time": "2019-12-11T17:38:00Z",
"end_time": "2019-12-11T17:39:40Z",
"score": [
0,
0
]
}
however when I access the Battle route, the nested Rounds are returned, but only the database fields, not the properties:
http://127.0.0.1:8001/battle/battles/1/
{
"url": "http://127.0.0.1:8001/battle/battles/1/",
"id": 1,
"status": "live",
"start_time": "2019-12-11T17:38:00Z",
"round_length": "00:01:40",
...
"rounds": [
{
"url": "http://127.0.0.1:8001/battle/rounds/1/",
"beat": null,
"index": 0,
"battle": "http://127.0.0.1:8001/battle/battles/1/",
"contender_entry": null,
"opponent_entry": null
},
{
"url": "http://127.0.0.1:8001/battle/rounds/2/",
"beat": null,
"index": 1,
"battle": "http://127.0.0.1:8001/battle/battles/1/",
"contender_entry": null,
"opponent_entry": null
},
{
"url": "http://127.0.0.1:8001/battle/rounds/3/",
"beat": null,
"index": 2,
"battle": "http://127.0.0.1:8001/battle/battles/1/",
"contender_entry": null,
"opponent_entry": null
}
],
"current_round": null
}
I want the properties to be displayed in the nested Round objects in Battle. But I couldn't get it to work.
These are my models:
class Round(models.Model):
battle = models.ForeignKey(Battle, on_delete=models.CASCADE, related_name="rounds")
index = models.IntegerField()
contender_entry = models.OneToOneField(Entry, on_delete=models.DO_NOTHING, related_name="round_contender",
null=True)
opponent_entry = models.OneToOneField(Entry, on_delete=models.DO_NOTHING, related_name="round_opponent", null=True)
#property
def start_time(self):
return self.battle.start_time + (self.index * self.battle.round_length)
#property
def end_time(self):
return self.start_time + self.battle.round_length
#property
def score(self):
opponent_votes = self.votes.filter(favors="opponent").count()
contender_votes = self.votes.filter(favors="contender").count()
draw_votes = self.votes.filter(favors="draw").count()
return (opponent_votes + draw_votes, contender_votes + draw_votes)
class Battle(models.Model):
status = models.CharField(max_length=32, choices=BATTLE_STATUS_CHOICES, default="awaiting_approval")
contender = models.ForeignKey(User, on_delete=models.CASCADE, related_name="contender_battles")
opponent = models.ForeignKey(User, on_delete=models.CASCADE, related_name="opponent_battles")
start_time = models.DateTimeField(default=timezone.now)
round_length = models.DurationField(default=timedelta(days=3))
And the serializers:
class RoundSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source="pk", read_only=True)
class Meta:
model = Round
fields = ["id", "battle", "index", "contender_entry", "opponent_entry", "start_time", "end_time", "score"]
read_only_fields = ["id", "battle", "index", "start_time", "end_time", "score"]
class BattleSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
current_round = RoundSerializer(read_only=True)
class Meta:
model = Battle
fields = ["url", "id", "status", "start_time", "round_length",
"opponent", "contender", "rounds", "current_round"]
read_only_fields = ["contender", "rounds", "status"]
class BattleReadSerializer(BattleSerializer):
contender = UserSerializer(read_only=True)
opponent = UserSerializer(read_only=True)
class Meta:
model = Battle
fields = ["url", "id", "status", "start_time", "round_length",
"opponent", "contender", "rounds", "current_round"]
read_only_fields = ["contender", "rounds"]
depth = 1
Note that I have 2 Battle serializers: BattleSerializer used on POST and PUT and users hyperlinks instead of nested fields. BattleReadSerializer is used for GET and nests the output. BattleReadSerializer is the one used in the example above.
I already tried adding the fields to the RoundSerializer explicitly, like so:
class RoundSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source="pk", read_only=True)
start_time = serializers.DateTimeField(read_only=True)
score = serializers.ListField(read_only=True)
But that didn't change anything. Is there any way to get the property fields to show (other than making them db fields and re-calculating them every so often)?
Try to add RoundSerializer serializer explicitly to the BattleReadSerializer serializer as below,
class BattleReadSerializer(BattleSerializer):
contender = UserSerializer(read_only=True)
opponent = UserSerializer(read_only=True)
rounds = RoundSerializer(read_only=True, many=True)
class Meta:
model = Battle
fields = ["url", "id", "status", "start_time", "round_length",
"opponent", "contender", "rounds", "current_round"]
read_only_fields = ["contender", "rounds"]
depth = 1 # remove this

How to clean the json output of a #property variable in Django Rest Framework

The issues is, when I do a get request for this model, the JSON response is not clean making it difficult to work with in Retrofit.
Here is an example JSON output:
[
{
"id": 1,
"restName": "Buddy's",
"banner": "http://127.0.0.1:8000/media/buddysImg.jpg",
"zipcode": 48212,
"restRating": {
"rating__avg": 4.0
},
"website": "Buddys.com",
"phone": 3138929001,
"restPrice": 0.0
}
]
And this is how I'd like it to look:
[
{
"id": 1,
"restName": "Buddy's",
"banner": "http://127.0.0.1:8000/media/buddysImg.jpg",
"zipcode": 48212,
"restRating": 4.0,
"website": "Buddys.com",
"phone": 3138929001,
"restPrice": 0.0
}
]
Here is my model :
class Rest(models.Model):
restName = models.CharField(max_length=50, null=False, default = " ")
zipcode = models.PositiveIntegerField( null=False, default = 0)
#restRating = models.FloatField( null=False, default = 0)
banner = models.ImageField( null=True)
website = models.CharField(max_length=50, null=False, default = " ")
phone = models.PositiveIntegerField( null=False, default = 0)
restPrice = models.FloatField( null=False, default = 0)
#property
def restRating(self):
avg = Rating.objects.filter(restId=self.pk).aggregate(Avg('rating'))
return avg
def __str__(self):
return self.restName
And here is my serializer :
class restSerializer(serializers.ModelSerializer):
restRating = serializers.FloatField
class Meta:
model = Rest
fields = ['id', 'restName', 'banner', 'zipcode', 'restRating', 'website', 'phone', 'restPrice']
Thank you for the help.
you can return only the value from your property. You need to change your property to this:
#property
def restRating(self):
avg = Rating.objects.filter(restId=self.pk).aggregate(Avg('rating'))['rating__avg']
return avg
from the aggregate docs:
aggregate() is a terminal clause for a QuerySet that, when invoked,
returns a dictionary of name-value pairs. The name is an identifier
for the aggregate value; the value is the computed aggregate. The name
is automatically generated from the name of the field and the
aggregate function.

python - Django ORM retrieve parent model with filtered children model

I have the following models in Django:
class Gasto(models.Model):
monto = models.DecimalField(default=0,decimal_places=2, max_digits=10)
nombre = models.CharField(max_length=30)
informacion = models.TextField(blank=True, default="")
numero_cuotas = models.PositiveSmallIntegerField(default=1)
es_compartido = models.BooleanField(default=False)
es_divisible = models.BooleanField(default=False)
contrato = models.ForeignKey(Contrato, on_delete=models.PROTECT)
class Cuota_Gasto(models.Model):
monto = models.DecimalField(default=0,decimal_places=2, max_digits=10)
fecha = models.DateField()
gasto = models.ForeignKey(Gasto,
related_name='cuotas',on_delete=models.DO_NOTHING)
factura_rendida = models.ForeignKey(Factura, on_delete=models.DO_NOTHING,
null=True, related_name='gasto_rendido')
factura_liquidada = models.ForeignKey(Factura,
on_delete=models.DO_NOTHING, null=True,
related_name='gasto_liquidado')
From now on: 'Gasto' will be called Expense, and 'Cuota_Gasto' will be called Share.
Expense is the parent model and Share is the children, with a 1..N relationship.
I want to retrieve each Expense with its Shares, BUT only those Shares that meet certain conditions. Is this possible?
I could get each expense with ALL shares by using related_name and reverse relantionship, for example:
{
"monto": "-500.00",
"nombre": "RRR",
"informacion": "Extra",
"numero_cuotas": 1,
"es_compartido": true,
"cuotas": [
{
"id": 16,
"nombre": "RRR",
"informacion": "Extra",
"contrato": 3,
"monto": "-500.00",
"fecha": "07/07/2019",
"gasto": 4,
"factura_rendida": null,
"factura_liquidada": 12
}
]
}
But I can't get to the query that allows me to exclude the shares i don't want.
I was thinking about something like this:
Gasto.objects.distinct().filter(cuotas__fecha__lte = date)
Even though that query is not correct.
I hope someone can help me.
Thanks

How to serialize field from intermediate model into the main model of a many to many through relationship?

I am using django 2 with Django Rest Framework and I have the following models:
class Product(models.Model):
name = models.Charfield()
description = models.TextField()
class ProductStorageCenter(models.Model):
product_id = models.ForeignKey(Product)
storage_center_id = models.ForeignKey(StorageCenter)
quantity = models.IntegerField()
class StorageCenter(models.Model):
name = models.Charfield()
city = models.ForeignKey(City)
products = models.ManyToManyField(Product, through="ProductStorageCenter")
I would like to know which products I have in each StorageCenter and also the quantity of a product available in that StorageCenter.
How could show a list of StorageCenters containing all its Products and the quantity of each Product for that StorageCenter?
The JSON return would be like this:
[
{ "id": 1,
"name": "My Storage Center",
"city": 1,
"products": [
{
"id": 1,
"name": "My Product 1",
"description": "My Product Description 1",
"quantity": 200
},
{
"id": 2,
"name": "My Product 2",
"description": "My Product Description 2",
"quantity": 500
}
]
},
{
"id": 2,
"name": "My Storage Center 2,
"city": 2,
"products": [
{
"id": 1,
"name": "My Product 1",
"description": "My Product Description 1",
"quantity": 350
}
]
}
]
EDIT:
Now I can get the JSON above but I don't think this is the best solution. In My StorageCenterSerializer I get the products of that StorageCenter with a function instead of just use ProductSerializer. Then I loop the Product queryset and later retrive the quantity for that Product in that StorageCenter.
Here are my serializers now:
class ProductSerializer(serializers.ModelSerializer):
quantity = serializers.IntegerField(read_only=True)
class Meta:
model = Product
fields = ('id', 'name', 'description', 'quantity')
class StorageCenterSerializer(serializers.ModelSerializer):
products = serializers.SerializerMethodField()
class Meta:
model = StorageCenter
fields = ('id', 'name', 'city', 'products')
def get_products(self, obj):
products = Product.objects.filter(productstoragecenter__storage_center_id = obj.id)
for product in products:
psc = ProductStorageCenter.objects.get(storage_center_id = obj.id, product_id = product.id)
product.quantity = psc.quantity
serializer = ProductSerializer(products, many=True)
return serializer.data
You should use Nested relationships with many=True.
You didn't post your serializers, so here's the example from DRF's documentation:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('order', 'title', 'duration')
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
When you'll serialize Album. you'll get the nested tracks:
{
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
'tracks': [
{'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
{'order': 2, 'title': 'What More Can I Say', 'duration': 264},
{'order': 3, 'title': 'Encore', 'duration': 159},
...
],
}
As for Your Question, you are missing the City Model due to which i could not attend to your code directly but i can refer you an example which does the thing you need,
NOTE: As a Software Engineer, I love to use Architectures and I have deeply worked on Layered Approach for Development so I am gonna be Answering it with Respect to Tiers.
serializer = SerializerNameGoesHere(AllFilteredObjectsfromModel, many=True)
would serializer m2m.
Here's an Example in Layered Approach!
As i understood the Issue, Here's the Solution
models.py
class Member(models.Model):
member_id = models.AutoField(primary_key=True)
member_name = models.CharField(max_length =
class Group(models.Model):
group_id = models.AutoField(primary_key=True)
group_name = models.CharField(max_length = 20)
fk_member_id = models.ForeignKey('Member', models.DO_NOTHING,
db_column='fk_member_id', blank=True, null=True)
class Membership(models.Model):
membershipid = models.AutoField(primary_key=True)
fk_group_id = models.ForeignKey('Group', models.DO_NOTHING,
db_column='fk_member_id', blank=True, null=True)
join_date = models.DateTimeField()
serializers.py
import serializer
class AllSerializer(serializer.Serializer):
group_id = serializer.IntegerField()
group_name = serializer.CharField(max_length = 20)
join_date = serializer.DateTimeField()
CustomModels.py
imports...
class AllDataModel():
group_id = ""
group_name = ""
join_date = ""
BusinessLogic.py
imports ....
class getdata(memberid):
alldataDict = {}
dto = []
Member = models.Members.objects.get(member_id=memberid) #or use filter for Name
alldataDict["MemberId"] = Member.member_id
alldataDict["MemberName"] = Member.member_name
Groups = models.Group.objects.filter(fk_member_id=Member)
for item in Groups:
Custommodel = CustomModels.AllDataModel()
Custommodel.group_id = item.group_id
Custommodel.group_name = item.group_name
Membership = models.Membership.objects.get(fk_group_id=item.group_id)
Custommodel.join_date = Membership.join_date
dto.append(Custommodel)
serializer = AllSerializer(dto,many=True)
alldataDict.update(serializer.data)
return alldataDict
now what you can do is:
#Member = models.Members.objects.get(member_id=memberid)
Member = models.Members.objects.filter(member_id=memberid)
#And then Loop over it
for item in Member:
#The Rest of the code would go here!

django timeseries postgres beginner

I'm writing small application in django in order to learn databases and web etc.
The application should display players data and record statistics over time.
To answer questions such as:
How many random battles palyer has played in last month/week/day? - graph
What players left/entered clan? - etc.
I've been searching and reading and I found these suggestions:
What is a good combination of technology to use?
MongoDB, InfluxDB, PostgreSQL, RedisDB, or from packages django-timeseries, django-reversion.
I did not find my approach to be good and I probably, can anybody suggest me which database to use and how models should look like then a bit?
Critics or advice with database design is very appreciated.
Data is being downloaded from 3rd party API as jsons.
Structures which are downloaded:
Player.json
{
"501435906": { # this is PlayerID
"last_battle_time": 1484160229,
"statistics": {
"all": {
"battles": 70555
},
"random": {
"battles": 67361
}
}
}, # then next players continue
}
clan.json
{
"500004323": { # clan ID
"members": [
{
"account_id": 501435906, # PlayerID same as in Player.json
"account_name": "Player1",
"joined_at": 1447589992,
"role": "private",
"role_i18n": "Private"
},]
"name": "Full Clan Name",
"tag": "TAG",
}
stronghold.json
{
"500323931": { # PlayerID
"stronghold_skirmish": null,
"total_resources_earned": 0,
"week_resources_earned": 0
}, # next player follows
}
My approach:
Merge data together
{
"500004323": {
'name': 'Full Clan Name',
'tag': 'TAG'
"members": {
"500012979": {
"account_id": "500012979",
"account_name": "Player1",
"joined_clan_date": 1415990768,
"last_battle_time": 1484160229,
"role": "Commander",
"statistics": {
"all": {
"battles": 70555
},
"random": {
"battles": 67361
}
},
"stronghold": {
"stronghold_skirmish": {
"battles": 2223
},
"total_resources_earned": 32582,
"week_resources_earned": 80
}
}, # next members
}, # next clan
}
And import this data into following models:
class Clan(models.Model):
"""Clan model"""
clan_id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=100)
tag = models.CharField(max_length=5)
#property
def members(self):
return Player.objects.filter(clan=self)
def kick_player(self, player):
player.leave_clan()
class PlayerLeaversManager(models.Manager):
def leave_clan(self, players):
"""Take list of players and remove them from their clan
:param players:
"""
for player in players:
player.leave_clan()
class Player(models.Model):
"""Player model"""
account_id = models.IntegerField(primary_key=True)
access_token = models.TextField(blank=True,
null=True)
access_token_expires_at = models.CharField(max_length=10,
blank=True,
null=True)
account_name = models.CharField(max_length=250)
clan = models.ForeignKey('Clan',
on_delete=models.CASCADE,
blank=True,
null=True,
related_name='current_clan')
previous_clan = models.ForeignKey('Clan',
on_delete=models.CASCADE,
blank=True,
null=True,
related_name='previous_clan')
# objects = models.Manager()
objects = PlayerLeaversManager()
def __str__(self):
return '{0} - {1}'.format(self.account_name, self.account_id)
def get_absolute_url(self):
return reverse('wot:player_detail',
args=[self.account_name])
def leave_clan(self):
self.previous_clan = self.clan
self.clan = None
self.save()
class PlayerData(models.Model):
"""Players data daily tracked"""
created = models.DateTimeField(auto_now_add=True)
player = models.ForeignKey('Player',
on_delete=models.CASCADE,
null=True,
blank=True
)
joined_clan_date = models.DateTimeField(blank=True,
null=True)
role_in_clan = models.CharField(max_length=250,
blank=True,
null=True)
battles_on_random = models.IntegerField(blank=True,
null=True)
battles_all = models.IntegerField(blank=True,
null=True)
battles_stronghold = models.IntegerField(blank=True,
null=True)
tank = models.ManyToManyField('Vehicle',
related_name='tanks',
blank=True,)
last_battle_time = models.DateTimeField(blank=True,
null=True)
# stronghold stats
total_resources_earned = models.IntegerField(blank=True, null=True)
week_resources_earned = models.IntegerField(blank=True, null=True)
Whole code can be found on my github https://github.com/1oglop1/anv_wot
Thank you for all suggestions.
After some more research and asking at python FB community I will try following:
1) Normalize the models
2) Use PostgreSQL as backend.

Categories