Running query on lazy dynamic relationship at parent call - flask

I have a Flask app with sql-alchemy which has a number of models using relationships with lazy=dynamic.
Example model:
class TeamModel(db.Model):
__tablename__ = "teams"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(1000))
created_on = db.Column(db.DateTime(timezone=True), server_default=func.now())
updated_on = db.Column(db.DateTime(timezone=True), onupdate=func.now())
players = db.relationship("PlayerModel", backref="team", cascade="all,delete", lazy="dynamic")
I'm using Marshmallow to serialise models to return to the user:
team = TeamModel.get_by_id(team_id)
team_dump = TeamSchema().dump(team)
return {
"team": team_dump,
}, 200
Marshmallow schema isn't relevant but looks like this:
class TeamSchema(Schema):
id = fields.Int()
name = fields.String(required=True)
created_on = fields.DateTime()
updated_on = fields.DateTime()
players = fields.Nested("PlayerSchema", many=True)
This all works and is nice and clean, but the issue is the order of the players returned to the user inside the team is inconsistent. Is there a way to apply an order_by query on the players as the team is passed into the Marshmallow schema?
I can't seem to find examples of how best to handle this.

Related

Django - How to reach relational field

I have Patient model which has many to many relation with ICF model. ICF model has many to one field to Unite model. In Unite model I have Unite names. I want to reach Unite names that are assigned to Patient with relations. I try to reach Unite names for each Patient. If it is more than one I cannot list them for that person. Here are my codes.
This is my patient model.
class Patient (models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
lastname = models.CharField(max_length=255, default="doe")
data = models.JSONField()
Intensivecare_form = models.ManyToManyField(Intensivecare_Form)
isDeleted = models.BooleanField(default=False)
This is my ICF model.
class Intensivecare_Form (models.Model):
id = models.AutoField(primary_key=True)
hospitals_id = models.ForeignKey(Hospital, on_delete=models.CASCADE)
formname = models.CharField(max_length=128)
data = models.JSONField()
unites_id = models.ForeignKey(Unite, on_delete=models.CASCADE)
At last, This is my Unite model
class Unite (models.Model):
id = models.AutoField(primary_key=True)
unitename = models.CharField(max_length=128)
In my views.py file I have a function as below
def listPatients(request):
Patients = Patient.objects.all()
output = []
for patient in Patients:
unitename = []
icfinfo = serializers.serialize('json', patient.Intensivecare_form.all())
jsonicfinfo = json.loads(icfinfo)
unitename.append(jsonicfinfo)
output.append({'id': patient.id,
'fname': patient.name,
'lname': patient.lastname,
'data': patient.data,
'unitename': unitename,
})
return JsonResponse(output, safe=False)
This is what output looks like. I need to reach formname in unitename
0-> id, fname..., unitename-> 0 -> fields -> unitename
It seems your ICForm is only pointing at one Unite object.
Maybe try this to get all ICForm's in the query.
Instead of
unitename = []
icfinfo = serializers.serialize('json', patient.Intensivecare_form.all())
jsonicfinfo = json.loads(icfinfo)
unitename.append(jsonicfinfo)
Try renaming Intensivecare_form model rename unites_id to unites -- django knows to link on the id automatically and calling it unites_id could cause naming conflicts
Intensivecare_Form(models.Model):
unites = models.ForeignKey(Unites, on_delete=models.CASCADE)
And to get all unites names just get it in one query
unite_names = list(patient.Intensivecare_form.all().values_list('unites__unitename', flat=True))
That should do it for you! Best of luck!

Query ManyToMany models to return number of instance of A that are linked to B and reverse?

EDIT 22/01/2021
Thanks for advices in models naming.
Here is diagram of my database: https://dbdiagram.io/d/5fd0815c9a6c525a03ba5f6f?
As you can see in this diagram, I have simplyed as Customers_Orders is in fact a ternary relationship with models comments. I decided to use an 'declared' throught models for this ternary relationship
Do I realy need to add a ManyToMany fields in Orders?
I have a look at Django's doc example with person, group and membership (https://docs.djangoproject.com/fr/3.1/topics/db/models/) and a manytomany fiels is added only in Group model, not in Person model
I have 2 models (Customers and Orders) with a declared throught models (Customers_Orders) to manage manytomany relationship.
But I did'nt understand how to query to have :
for one Customer, all its orders: How many orders were made by each customer
for one Order, all its customers: How many customers were associated for each order
I have understood, I should do:
c = Customers.objects.get(customer_id=1)
c.CustomersOrders.all() but it failled AttributeError: 'Customers' object has no attribute 'CustomersOrdersComments'
class Customers(SafeDeleteModel):
customer_id = models.AutoField("Customer id", primary_key = True)
orders = models.ManyToManyField(Orders, through = Customers_Orders, related_name = "CustomersOrders")
created_at = models.DateTimeField("Date created", auto_now_add = True)
class Orders(SafeDeleteModel):
order_id = models.AutoField("Order id", primary_key = True)
created_at = models.DateTimeField("Date created", auto_now_add = True)
class Customers_Orders(SafeDeleteModel):
order = models.ForeignKey("Orders", on_delete = models.CASCADE)
customer = models.ForeignKey("Customers", on_delete = models.CASCADE)
You can do this - Given these models:
class Customer(models.Model):
name = models.CharField(max_length=30, default=None, blank=True, null=True)
orders = models.ManyToManyField("Order", through="CustomerOrder", related_name="orders")
created_at = models.DateTimeField(auto_now_add=True)
class Order(models.Model):
created_at = models.DateTimeField(auto_now_add = True)
customers = models.ManyToManyField(Customer, through="CustomerOrder", related_name="customers")
class CustomerOrder(models.Model):
order = models.ForeignKey("Order", on_delete = models.CASCADE)
customer = models.ForeignKey("Customer", on_delete = models.CASCADE)
customer = Customer.objects.first()
# Get count of their orders
customer_orders_count = customer.orders.all().count()
order = Order.objects.first()
# Get count of order's customers
order_customers_count = order.customers.all().count()
The docs explains this quite well:
https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_many/
In your case, that would be something like:
customer = Customers.objects.get(customer_id=1) # Fetch a specific customer from DB
customer_orders = customer.orders.all() # Return a QuerySet of all the orders related to a given `customer`
c.CustomersOrders.all() can't work, because CustomersOrders is the class name of your through model, and your Customers model does not have any CustomersOrders field.

In Django REST framework, how do you access a field in a table referenced by a foreign key

I have the following models in Django:
class campaign(models.Model):
start_date = models.DateField('Start Date')
end_date = models.DateField('End Date')
description = models.CharField(max_length=500)
name = models.CharField(max_length=200)
active_start_time = models.TimeField()
active_end_time = models.TimeField()
last_updated = models.DateTimeField('Date updated',auto_now=True)
active = models.BooleanField(default=True)
client_id = models.ForeignKey('client',on_delete=models.PROTECT)
def __unicode__(self):
return u'%d | %s | %s' % (self.id,self.name, self.description)
class campaign_product(models.Model):
product_id = models.ForeignKey('product',on_delete=models.PROTECT)
last_updated = models.DateTimeField('Date updated',auto_now=True)
campaign_id = models.ForeignKey('campaign',on_delete=models.PROTECT)
class product(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=500)
sku = models.CharField(max_length=200,blank=True,null=True)
retail_price = models.DecimalField(decimal_places=2,max_digits=11)
discount_price = ((1,'Yes'),(0,'No'))
discounted_price = models.DecimalField(decimal_places=2,max_digits=11,blank=True,null=True)
category_id = models.ForeignKey('category',on_delete=models.PROTECT)
last_updated = models.DateTimeField('Date updated',auto_now=True)
active = models.BooleanField(default=True)
def __unicode__(self):
return u'%d | %s' % (self.id, self.name)
I also have the following serializer:
class campaignProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = campaign_product
fields = ('product_id', 'campaign_id')
And the following view set behavior in the urls.py file:
class campaignProductViewSet(viewsets.ModelViewSet):
queryset = campaign_product.objects.filter(campaign_id__start_date__lte=datetime.now(),campaign_id__end_date__gte=datetime.now(),campaign_id__active__exact=True)
serializer_class = campaignProductSerializer
My problem is I need to include the name field from the products model in my query results when for instance a request is made on http://127.0.0.1:8000/campaign_product/1/. Currenly this request returns only the product_id and the campaign_id. I tried making the serializer as follows:
class campaignProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = campaign_product
fields = ('product_id', 'campaign_id', 'product.name')
But then the service returns the following error:
Field name `product.name` is not valid for model `campaign_product`.
I event tried using product__name with and without quotes. Without quotes it tells me that there is no such variable, and with quotes it gives the is not valid for model error similar to the above. Heeelp! Getting this extra field is proving to be a pain :-(
What you want will need to look something more like this:
class campaignProductSerializer(serializers.HyperlinkedModelSerializer):
product_name = serializers.CharField(source='product_id.name')
class Meta:
model = campaign_product
fields = ('product_id', 'campaign_id', 'product_name')
P.S. As an unrelated side note, it is generally a convention in Python code to name classes with CamelCase, such as Campaign, CampaignProduct, Product, and CampaignProductSerializer.
Edit: P.P.S. Originally, I had put written the product_name field with source='product.name'. This was actually due to me looking at the code too quickly and making assumptions based on Django conventions. Typically, with a Django ForeignKey, you would name the ForeignKey field after the model you are linking to, rather than explicitly naming it with _id. For example, the CampaignProduct model would typically be written with product = ForeignKey(...) and campaign = ForeignKey(...). In the background, Django will actually use product_id and campaign_id as the database field names. You also have access to those names on your model instances. But the product and campaign variables on your model instances actually return the objects which you are referring to. Hopefully that all makes sense.

constructing a joined django query

I'm interacting with a legacy db on another system, so the models are written in stone and not very django-ey.
My models.py:
class Site(models.Model):
site_code = models.CharField(max_length=30, primary_key=True)
name = models.CharField(unique=True, max_length=300)
class Document(models.Model):
id = models.IntegerField(primary_key=True)
site_ref = models.ForeignKey(Site)
description = models.CharField(max_length=1500)
class DocumentStatusCategory(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(unique=True, max_length=90)
class DocumentStatus(models.Model):
id = models.IntegerField(primary_key=True)
document = models.ForeignKey(Document)
status = models.ForeignKey(DocumentStatusCategory)
changed_by = models.ForeignKey(User)
created_at = models.DateTimeField()
In my views.py I want to retrieve a queryset with all the Document objects that belong to a specified Site (say site_ref=mysite) which do not have any related DocumentStatus objects with status=4.
Any idea how I can do this as a single (non-sql intensive) line?
Document.objects.filter(site_ref=mysite).exclude(documentstatus__status_id=4)
Document.objects.filter(site_ref=site_obj).exclude(documentstatus_set__in=DocumentStatus.objects.filter(status_id=4))
Not exactly one query, but I don't think that's achievable without going down to raw sql. Two queries isn't bad though I suppose.
I should mention that the above assumes that the reverse relation between Document and DocumentStatus is documentstatus_set. You can explicitly state what the reverse relation is like so:
# inside the DocumentStatus model definition
document = models.ForeignKey(Document, related_name='document_statuses')
Then the query becomes:
Document.objects.filter(site_ref=site_obj).exclude(document_statuses__in=DocumentStatus.objects.filter(status_id=4))

Filtering by ForeignKey with a legacy model doesn't work without .pk

I have a legacy database that I've set up some models to use. The models look like this:
class UserProfile(models.Model):
user = models.OneToOneField(User, primary_key=True, db_column='uid')
email = models.CharField(max_length=255, unique=True)
username = models.CharField(unique=True, max_length=150)
class Meta:
db_table = u'legacy_user'
class OtherModel(models.Model):
user = models.ForeignKey('my_app.UserProfile', db_column='uid')
some_data = models.IntegerField()
another_model = models.ForeignKey('other_app.AnotherModel', db_column='related')
class Meta:
db_table = u'legacy_other_model'
When I perform this queryset:
my_user = UserProfile.objects.get(username='foo')
count = OtherModel.objects.filter(user=my_user).count()
I get SQL that looks like:
SELECT COUNT(*) FROM `legacy_other_model` WHERE `legacy_other_model`.`uid` = None
But if I change the count query to this (note the .pk):
count = OtherModel.objects.filter(user=my_user.pk).count()
I get SQL that looks like:
SELECT COUNT(*) FROM `legacy_other_model` WHERE `legacy_other_model`.`uid` = 12345
This doesn't seem to be the expected behavior, looking at: http://docs.djangoproject.com/en/dev/topics/db/queries/#queries-over-related-objects
Did I set up something wrong in my models?
Suspect you might need to specify to_field on your OneToOne definition:
user = models.ForeignKey('my_app.UserProfile', db_column='uid', to_field='user')
Does that help?
At this point, I believe this is a bug in Django.
For future reference, I've reported it here:
http://code.djangoproject.com/ticket/15164