django rest framework category parent and child count - django

There is my model:
class Category(models.Model):
.....
slug = models.SlugField(verbose_name=_('Slug'))
description = RedactorField(verbose_name=_('Description'))
parent = models.ForeignKey('self', null=True, blank=True,
default=None, verbose_name=_('Parent'))
The thing is I need to make a api resource, using DjangoRestFramework, and serializer should contain count of childs for each category.
Something like this, i made with inbox DRF tools like generics.ListAPIView:
[
{
"id": 1,
"created": "01-08-2017 10:42 UTC",
"modified": "01-08-2017 10:55 UTC",
"name": "Category_1",
"slug": "",
"description": "",
"parent": null,
"child_count": 12,
},
{
"id": 2,
"created": "01-08-2017 10:42 UTC",
"modified": "01-08-2017 10:55 UTC",
"name": "SubCategory_1_1",
"slug": "",
"description": "",
"parent": 1,
"child_count": 0,
},
...
]
so the queryset
Category.objects.annotate(child_count=models.Count('parent'))
gonna show only the count of parents (and its always equals to 1 or 0).
There is MPTTModel lib, that possibly could solve this, but i can't use it, because of some project specific issues exists.

This is should work:
Category.objects.annotate(child_count=Count('category_set'))
'category_set' is the name that django auto-generates to use it in reverse relations, you should add related_name='children' to the parent field and then you can use Count('children').

Related

Django dumpdata/loaddata – Key is not present in table

I'm trying to dump my postgres data and load it locally, but I'm ending up with an error
These are my two models:
class User(AbstractUser):
pass
class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
primary_key=True
)
dob = models.DateField(null=True, blank=True)
This is the code I'm executing for the data dump:
call_command('dumpdata', indent=4, exclude=['contenttypes', 'auth.Permission', 'sessions', 'admin.logentry'])
However, when trying to load it, I get the following error:
django.db.utils.IntegrityError: Problem installing fixtures: insert or update on table "profiles_profile" violates foreign key constraint "profiles_profile_user_id_a3e81f91_fk_accounts_user_id"
DETAIL: Key (user_id)=(1) is not present in table "accounts_user".
What I noticed when trying to read the fixture is that there are no pks for the user, and no references from the profile:
{
"model": "accounts.user",
"fields": {
"password": "xyz",
"last_login": "2022-11-27T17:28:45.854Z",
"is_superuser": true,
"username": "JohnDoe",
"first_name": "John",
"last_name": "Doe",
"is_staff": true,
"is_active": true,
"date_joined": "2020-09-02T16:28:13.329Z",
"groups": [],
"user_permissions": []
}
},
{
"model": "profiles.profile",
"pk": 1,
"fields": {
"dob": "1980-06-20",
}
},
Is that normal? Note that I also tried using the natural-foreign and natural-primary-keys arguments in the call_command as well, without any effect.
What am I doing wrong?

Django - Get a single timestamp per foreign key ID (MySQL)

Im having trouble getting only a single record per foreign key ID on the below model, I have tried this query, but it doesnt seem to be doing anything at all currently
I have used a RAW query which works, but I can't use a filter on it. ive also created a list and removed duplicates from the QuerySet but again I can't filter it because its a list
Query:
queryset = BGPData.objects.annotate(max_timestamp=Max('timestamp')).filter(timestamp=F('max_timestamp')).select_related(
'device_circuit_subnet__subnet',
'device_circuit_subnet__device',
'device_circuit_subnet__circuit',
'device_circuit_subnet__device__site',
)
Model:
class BGPData(models.Model):
device_circuit_subnet = models.ForeignKey(DeviceCircuitSubnets, verbose_name="Device", on_delete=models.CASCADE)
bgp_peer_as = models.CharField(max_length=20, verbose_name='BGP Peer AS', blank=True, null=True)
bgp_session = models.CharField(max_length=10, verbose_name='BGP Session', blank=True, null=True)
bgp_routes = models.CharField(max_length=10, verbose_name='BGP Routes Received', blank=True, null=True)
service_status = models.CharField(max_length=10, verbose_name='Service Status', blank=True, null=True)
timestamp = models.DateTimeField(auto_now=True, blank=True, null=True)
Sample Data im testing with (printed as a dict), there should only be one record for "device_circuit_subnet_id" : "10", the one which is newest.
I would like the latest record per device_circuit_subnet_id, so the query should return 3 results instead of 4, as there are 2 items with the same device_circuit_subnet_id.
ive read that distinct is used for this but were running MySQL, is there another way?
Thanks
[{
"id": 4,
"device_circuit_subnet_id" : "10",
"hostname": "EDGE",
"circuit_name": "MPLS",
"subnet": "172.1.1.1",
"subnet_mask": "/30",
"bgp_session": "1w2d",
"bgp_routes": "377",
"bgp_peer_as": "1",
"service_status": "Up",
"timestamp": "2019-11-18 16:16:17"
},
{
"id": 5,
"device_circuit_subnet_id" : "11",
"hostname": "INT-GW",
"subnet": "1.1.1.1",
"subnet_mask": "/24",
"bgp_session": null,
"bgp_routes": null,
"bgp_peer_as": null,
"service_status": "unknown",
"timestamp": "2019-08-07 14:46:00"
},
{
"id": 8,
"hostname": "EDGE",
"device_circuit_subnet_id" : "20",
"circuit_name": "MPLS 02",
"subnet": "172.2.1.1",
"subnet_mask": "/30",
"bgp_session": null,
"bgp_routes": null,
"bgp_peer_as": null,
"service_status": "unknown",
"timestamp": "2019-11-15 16:18:30"
},
{
"id": 9,
"hostname": "EDGE",
"device_circuit_subnet_id" : "10",
"circuit_name": "MPLS",
"subnet": "172.1.1.1",
"subnet_mask": "/30",
"bgp_session": "1w3d",
"bgp_routes": "385",
"bgp_peer_as": "1",
"service_status": "Up",
"timestamp": "2019-11-18 16:16:44"
}
]
Thanks
Have you tried this?
from django.db.models import Max, F
max_timestamp = Max('device_circuit_subnet__bgpdata__timestamp')
result = BGPData.objects.annotate(ts=max_timestamp).filter(timestamp=F('ts')).select_related(...)
I'm not sure about the performance of this query, but it will work :) :)
Django querysets are evaluated in a lazy fashion. As such, this query will only fetch one record from the database. The ordering on timestamp is in descending order due to the - prefix, so the latest timestamp value will be the first record.
queryset = BGPData.objects.all().order_by(
#prefix field name to order by with `-` to use Descending order
'-timestamp'
).select_related(
'device_circuit_subnet__subnet',
'device_circuit_subnet__device',
'device_circuit_subnet__circuit',
'device_circuit_subnet__device__site',
)[0]

django rest serializer: ordering fields appearance

Is it possible to specify in which order fields will appear in a serialised model?
To make sure there is no confusion, while searching answers for this I have found lots of suggestions for ordering objects in a list view but this is not what I am looking for.
I really mean for a given model, I'd like their fields, once serialized to appear in a specific order. I have a fairly complex serialized object containing a lot of nested serializers, which appear first. I'd prefer instead key identifying fields such as name and slug to show up first, for readability.
Apologies in advance if this question is a duplicate, but I didn't find any relevant responses.
Solution
Based on #Toni-Sredanović solution I have implemented the following solution
def promote_fields(model: models.Model, *fields):
promoted_fields = list(fields)
other_fields = [field.name for field in model._meta.fields if field.name not in promoted_fields]
return promoted_fields + other_fields
class MySerializer(serializers.ModelSerializer):
...
class Meta:
model = MyModel
fields = promote_fields(model, 'id', 'field1', 'field2')
For that you can specify which fields you want to show and their order in class Meta:
class Meta:
fields = ('id', 'name', 'slug', 'field_1', 'field_2', ..., )
Here is a full example:
class TeamWithGamesSerializer(serializers.ModelSerializer):
"""
Team ModelSerializer with home and away games.
Home and away games are nested lists serialized with GameWithTeamNamesSerializer.
League is object serialized with LeagueSerializer instead of pk integer.
Current players is a nested list serialized with PlayerSerializer.
"""
league = LeagueSerializer(many=False, read_only=True)
home_games = GameWithTeamNamesSerializer(many=True, read_only=True)
away_games = GameWithTeamNamesSerializer(many=True, read_only=True)
current_players = PlayerSerializer(many=True, read_only=True)
class Meta:
model = Team
fields = ('id', 'name', 'head_coach', 'league', 'current_players', 'home_games', 'away_games', 'gender')
And the result:
{
"id": 1,
"name": "Glendale Desert Dogs",
"head_coach": "Coach Desert Dog",
"league": {
"id": 1,
"name": "Test league 1"
},
"current_players": [
{
"id": "rodriem02",
"first_name": "Emanuel",
"last_name": "Rodriguez",
"current_team": 1
},
{
"id": "ruthba01",
"first_name": "Babe",
"last_name": "Ruth",
"current_team": 1
}
],
"home_games": [
{
"id": 6,
"team_home": {
"id": 1,
"name": "Glendale Desert Dogs"
},
"team_away": {
"id": 2,
"name": "Mesa Solar Sox"
},
"status": "canceled",
"date": "2019-10-01"
},
{
"id": 7,
"team_home": {
"id": 1,
"name": "Glendale Desert Dogs"
},
"team_away": {
"id": 2,
"name": "Mesa Solar Sox"
},
"status": "",
"date": "2019-10-04"
}
],
"away_games": [
{
"id": 3,
"team_home": {
"id": 2,
"name": "Mesa Solar Sox"
},
"team_away": {
"id": 1,
"name": "Glendale Desert Dogs"
},
"status": "canceled",
"date": "2019-10-02"
}
],
"gender": "M"
}
If you would just use fields = '__all__' default ordering would be used which is:
object id
fields specified in the serializer
fields specified in the model
Best i can think of right now regarding your comment about generating fields is getting the fields in model, not really sure how to access what you've defined in the serializer so you would still need to write that manually.
Here is how you could do it with my example (this would make the name and gender appear on top):
class Meta:
model = Team
fields = ('name', 'gender')\
+ tuple([field.name for field in model._meta.fields if field.name not in ('name', 'gender')])\
+ ('league', 'home_games', 'away_games', 'current_players')

Django project with custom user model load fixture error

I am setting up a new django(version 2.2) project and want to use custom user model. When I load fixtures data, it failed with error like below:
django.db.utils.IntegrityError: Problem installing fixtures: insert or update on table "doctors_doctor" violates foreign key constraint "doctors_doctor_user_ptr_id_ba968804_fk_doctors_user_id"
DETAIL: Key (user_ptr_id)=(1) is not present in table "doctors_user".
From django document - https://docs.djangoproject.com/en/2.1/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project I realized I did 'python manage.py migrate' before changing AUTH_USER_MODEL in settings.py. So I tried to delete all tables and redo 'python manage.py migrate', but it still hit this problem.
Following are my code
settings.py
AUTH_USER_MODEL='doctors.User'
models.py
class User(AbstractUser):
mobile = models.CharField(max_length=30)
fixtures_autodump = ['dev_users']
class Meta:
db_table = 'doctors_user'
def __str__(self):
return self.username
class Doctor(User):
personal_id = models.CharField(max_length=255, blank=True)
fixtures_autodump = ['dev_users']
class Meta:
db_table = 'doctors_doctor'
def __str__(self):
return self.username
dev_users.json
[
{
"model": "doctors.doctor",
"pk": 1,
"fields": {
"date_joined": "2019-06-16T09:09:56.127Z",
"email": "user1#localhost.dev",
"first_name": "user1",
"groups": [],
"is_active": true,
"is_staff": true,
"is_superuser": true,
"last_login": null,
"last_name": "test",
"password": "pbkdf2_sha256$36000$nITgYnD9lKzm$cXGlthNYJDrrihQikgyh7HO5hm2fNvH71+fiCoMyIpY=",
"user_permissions": []
}
},
{
"model": "doctors.doctor",
"pk": 2,
"fields": {
"date_joined": "2019-06-16T09:09:56.127Z",
"email": "user2#localhost.dev",
"first_name": "user2",
"groups": [],
"is_active": true,
"is_staff": false,
"is_superuser": false,
"last_login": null,
"last_name": "test",
"password": "pbkdf2_sha256$36000$ohIbxnbyKjNm$smg+FvfhT1cF1kLt93EDz/n5KyfkDupIgkihsNIHQS8=",
"user_permissions": []
}
}
]
I expect loading fixture data can be successful, please help.
I needed 2 records in my JSON file, one is User, and the other is my custom user. The pk value of these 2 should be the same. That's the way Django loaddata works for custom user. I make this change and it works.

Object of type Company is not JSON serializable when writing tests

I'm having an issue in Django RestFramework in testing.
I have the following test:
def test_update_coupon(self):
response = self.make_coupon_request(
kind="put",
version="v1",
id=2,
data=self.valid_coupon_data
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
Where make_coupon_request has a return of:
return self.client.put(
reverse("coupon",
kwargs={
"version": kwargs["version"],
"pk": kwargs["id"]
}
),
data=json.dumps(kwargs["data"]),
content_type='application/json'
)
and valid_coupon_data where the problem is occurring is:
self.valid_coupon_data = {
"company": Company.objects.get(id=1),
"name": "Coupon Updated",
"added": "2018-11-30",
"code": "TESTCODE"
}
edit - An example Company that would be in this structure is:
{
"id": 1,
"name": "test",
"added": "2018-11-30"
},
So the total structure would look like:
self.valid_coupon_data = {
"company": {
"id": 1,
"name": "test",
"added": "2018-11-30"
},
"name": "Coupon Updated",
"added": "2018-11-30",
"code": "TESTCODE"
}
The error I am getting is in make_coupon_request that json.dumps cannot serialize valid_coupon_data:
"TypeError: Object of type Company is not JSON serializable"
I have a serializer for Company:
class CompanySerializer(serializers.ModelSerializer):
coupons = CouponSerializer(many=True, read_only=True)
class Meta:
model = Company
fields = ("name", "added", "coupons")
And for coupon:
class CouponSerializer(serializers.ModelSerializer):
class Meta:
model = Coupon
fields = ("company", "name", "added", "code")
Basically I know that somehow I need to use a serializer in order to make my test work, as json.dumps isn't accepting the raw Company object... but I am not sure how nor do I quite understand why.
Here are my 2 models for reference:
class Company(models.Model):
name = models.CharField(max_length=255, null=False)
class Meta:
verbose_name_plural = "Companies"
class Coupon(models.Model):
company = models.ForeignKey(
Company, on_delete=models.CASCADE, related_name='coupons')
name = models.CharField(max_length=100)
added = models.DateField(auto_now_add=True)
code = models.CharField(max_length=255, null=False)
The problem may lies in this statement,
self.valid_coupon_data = {
"company": Company.objects.get(id=1), # here
"name": "Coupon Updated",
"added": "2018-11-30",
"code": "TESTCODE"
}
It's clear that you are trying to send a json data and unfortunately it contains a non-serializable data of Company type.
According to the CouponSerializer you'd provided, the below json is enough to create/update the Coupon instance.
{
"company": 1, # provide only integer value
"name": "Coupon Updated",
"added": "2018-11-30",
"code": "TESTCODE"
}
The problem is that you are passing a python object via a json in your test, this section
self.valid_coupon_data = {
"company": Company.objects.get(id=1), # This is the error point! you are passing python object not a json.
"name": "Coupon Updated",
"added": "2018-11-30",
"code": "TESTCODE"
}
And passing just integer value like other answers will not work either. you should pass json of company object too. like this:
company = Company.objects.get(id=1)
self.valid_coupon_data = {
"company": {
"id": company.id,
"name": company.name,
"added": company.added
},
"name": "Coupon Updated",
"added": "2018-11-30",
"code": "TESTCODE"
}
Note
By the way if you are using django rest, then the way you are returning data in your views is not correct. use to_representation or serializer.data methods. it's not that well to use json.dump when you have a powerfull library like django-rest-framework. You can also return json as is with Response library of django-rest. like return Response(jsonData)
If you want more clear answer, please provide make_coupon_request method codes. to see what's going on in there.