Django annotate with other model attributes based on a common field - django

I have a User model,
class User(AbstractBaseUser):
id = models.IntegerField(primary_key=True)
email = models.EmailField(unique=True)
I have another model named Company. The Company model has a reference to User model via an Integer field.
class Company(models.Model):
user_id = models.IntegerField(db_index=True)
name = models.CharField(max_length=100)
size = models.IntegerField(default=1)
I wanted to extract the company information along with user information.
basically I want a user object dictionary like this {'id':1, 'email':'abc#gmail.com','name':'foobar.co','size':400}
I want to annotate the user objects with name and size. As of now, I tried in the serializer's to_representation method. However, for millions of users this is super slow.
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
def to_representation(self, instance):
response = super(UserSerializer, self).to_representation(instance)
company = Company.objects.filter(user_id=instance.id)
if company.exists():
company = company.first()
response['name'] = company.name
response['size'] = company.size
return response
How can I achieve this annotation in the query itself.

If the links in the comment do not help you, You can use SerializerMethodField for name, and size
class UserSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
email = serializers.EmailField(read_only=True)
name = serializers.SerializerMethodField()
size = serializers.SerializerMethodField()
def get_name(self, obj):
# get name from DB using the User object(obj)
return name
def get_size(self, obj):
# get size from DB using the User object(obj)
return size

Related

Use parameters in subfields with graphene-django without relay for pagination purpose

I'm using graphene with django and I'm struggling to do something that in my head should be very simple, but I don't find it documented anywhere in graphene docs or github nor did I see similar question here. The closest to it I found was:
https://www.howtographql.com/graphql-python/8-pagination/ but as you can see I'd have to declare the parameters in the parent resolver which I don't want to.
I have a query like this
getUser(id: $userIdTarget) {
id
username
trainings{
id
name
sessions{
id
name
}
}
}
}
I would like to implement a pagination in the sessions subfield. So this is what I would like:
getUser(id: $userIdTarget) {
id
username
trainings{
id
name
sessions(first:10){
id
name
}
}
}
}
and in the resolver I'd implement something like this:
def resolve_sessions(root, info, first=None, skip=None):
if skip:
return gql_optimizer.query(Session.objects.all().order_by('-id')[skip:], info)
elif first:
return gql_optimizer.query(Session.objects.all().order_by('-id')[:first], info)
else:
return gql_optimizer.query(Session.objects.all().order_by('-id'), info)
(gql_optimizer is just an optimization wrapper library I use)
However this doesn't work as the field sessions correspond to a list of a model Session that is a fk to Training according to my django models, so this is automatically resolved by graphene because these types are DjangoObjectType , so I'm not really sure how can one customize these resolvers (or if it's even possible).
I'll leave the relevant models and types below:
Session model
class Session(models.Model):
name = models.CharField(max_length=200, help_text='Session\'s name')
category = models.CharField(max_length=240, choices=SESSION_CATEGORIES, default="practice",
help_text='Session type. Can be of \'assessment\''
'or \'practice\'')
total_steps = models.IntegerField(default=1, help_text='Amount of steps for this session')
created_at = models.DateTimeField(editable=False, default=timezone.now, help_text='Time the session was created'
'(Optional - default=now)')
completed_at = models.DateTimeField(editable=False, null=True, blank=True, help_text='Time the session was finished'
'(Optional - default=null)')
is_complete = models.BooleanField(default=0)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="training_sessions", on_delete=models.DO_NOTHING)
training = models.ForeignKey("Training", related_name="sessions", on_delete=models.CASCADE)
def __str__(self):
return self.name
UserType
class UserType(DjangoObjectType):
class Meta:
model = get_user_model()
fields = "__all__"
#classmethod
def get_queryset(cls, queryset, info, **kwargs):
if info.variable_values.get('orgId') and info.variable_values.get('orgId') is not None:
return queryset.order_by('username')
return queryset
SessionType
class SessionType(DjangoObjectType):
class Meta:
model = Session
fields = "__all__"
convert_choices_to_enum = False
#classmethod
def get_queryset(cls, queryset, info, **kwargs):
if info.variable_values.get('userId') and info.variable_values.get('userId') is not None:
return queryset.filter(Q(user_id=info.variable_values.get('userId'))).order_by('-id')
return queryset
TrainingType
class TrainingType(gql_optimizer.OptimizedDjangoObjectType):
class Meta:
model = Training
fields = "__all__"
convert_choices_to_enum = False
It's possible to extend your types to add more fields that aren't in the Django model -- perhaps that is the technique you are looking for to inject more data into the query?
class TrainingType(gql_optimizer.OptimizedDjangoObjectType):
my_extra_field = graphene.Int() # for example
class Meta:
model = Training
fields = "__all__"
convert_choices_to_enum = False
You can also override the default resolvers that are created with DjangoObjectType.

Django rest framework : extend user model for customer - one to one field

I have a customer model in Bcustomer app that extends the django User model, So I will save the basic details such as name in User table and the remaining data (city, etc) in customer table.
When I call the below code through API, it shows the following error. But data is saving in the tables. I also want to implement the get and put calls for this api.
Got AttributeError when attempting to get a value for field `city` on serializer `CustomerSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'User' object has no attribute 'city'.
my Bcustomer/models.py
class BCustomer(models.Model):
customer = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True, blank=True )
address = models.CharField(max_length=50)
city = models.CharField(max_length=256)
state = models.CharField(max_length=50)
user = models.ForeignKey(settings.AUTH_USER_MODEL, db_index=True, on_delete=models.CASCADE, related_name='customer_creator')
# more fields to go
def __str__(self):
# return str(self.name) (This should print first and last name in User model)
class Meta:
app_label = 'bcustomer'
my Bcustomer/serializers.py
from django.contrib.auth import get_user_model
from models import BCustomer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
city = serializers.CharField()
class Meta:
model = get_user_model()
fields = ('first_name', 'email','city')
def create(self, validated_data):
userModel = get_user_model()
email = validated_data.pop('email', None)
first_name = validated_data.pop('first_name', None)
city = validated_data.pop('city', None)
request = self.context.get('request')
creator = request.user
user = userModel.objects.create(
first_name=first_name,
email=email,
# etc ...
)
customer = BCustomer.objects.create(
customer=user,
city=city,
user=creator
# etc ...
)
return user
my Bcustomer/views.py
class CustomerViewSet(viewsets.ModelViewSet):
customer_photo_thumb = BCustomer.get_thumbnail_url
permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
queryset = BCustomer.objects.all()
serializer_class = CustomerSerializer
my Bcustomer/urls.py
router.register(r'customer', views.CustomerViewSet, 'customers')
POST request format
{
"first_name":"Jsanefvf dss",
"city":"My City",
"email":"myemail#gmail.com",
#more fields
}
I also need to implement put and get for this api. Now data is saving in both tables but shows the error.
Sure it complains.
Validation goes well, so does the creation but once it's created, the view will deserialize the result and return it to the client.
This is the point where it goes south. Serializer is configured to think that city is a field of the default user while it actually is part of BCustomer. In order to work this around, you should set the source argument to the city field. You might need to update the serializer's create/update to reflect that change, not sure about that one.

fetch related resource with primary resource depend on some filter in django-rest

here are my models:
class Ratings(BaseModel):
resource_a = models.OneToOneField(ResourceA)
count = models.IntegerField(default=0)
total = models.IntegerField(default=0)
average = models.IntegerField(default=0)
def __unicode__(self):
return self.count
class UserRatings(BaseModel):
user = models.ForeignKey(UserProfile)
score = models.IntegerField()
rating = models.ForeignKey(Ratings)
def __unicode__(self):
return self.user.username
I want to fetch the ratings of ResourceA with rating of user who is logged-in in same API call.
I am able to fetch ratings of ResourceA using ModelSerializer but not able to fetch rating of user who is logged in.
I tried to do it using below code in ModelSerializer of Ratings.:
user_rating = serializer.CharField(source='get_user_rating')
but I can write get_user_rating function only in models and I think there is no way to access request.user in model itself.
Please suggest a way so that I can fetch UserRatings of user who is logged in along with Ratings.
You can use nested serializers to get all the data in 1 api call.
Simpleest method of that is to specify depth field in Meta class of serializer.
Class Meta:
depth = 1
But it has disadvantage that it will fetch all of the data for all foreign related fields till that depth even if you don't all of it.
You can try below approach which i like better
Class ResourceASerializer(serializers.ModelSerializer):
rating = RatingSerializer()
class Meta:
model = UserRatings
Class RatingsSerializer(serializers.ModelSerializer):
resource_a = ResourceASerializer()
class Meta:
model = Ratings
Class UserRatingSerializer(serializers.ModelSerializer):
rating = RatingSerializer()
class Meta:
model = UserRatings
or if you don't want to make 3 serializers:
Class UserRatingSerializer(serializers.ModelSerializer):
resource_a_rating = serailizers.SerializerMethodField()
class Meta:
model = UserRatings
fields = ('resource_a_rating', ) # add other fields too
def get_resource_a_rating(self, obj):
return obj.rating.resource_a // This data should be serializeable
To get the data for logged in user only over ride the get_queryset method in the view.
def get_queryset(self):
return UserRatings.objects.filter(user=self.request.user)

Django model/form clean, validate, and unique

I am trying to create a Model and ModelForm with "name" and "client" fields that have the following cleaning and validation characteristics. I can manage each individual requirement but can't seem get them to work together.
An authenticated user can enter a name for an Item
Item is saved with the name and forced to the client that is associated with the user account.
Name is cleaned via ' '.join(name.strip().split())
Name is validated so that (cleaned_name.lower(),client) is unique
EG: If "FOO BAR" exists in the user's associated client, user would get an error if they enter "foo bar"
It is a fairly simple model:
class Item(BaseModel):
class Meta:
unique_together = (("client", "name"),)
client = models.ForeignKey(Client,related_name='items',null=True,blank=False)
name = models.CharField(max_length=64, null=False, blank=False)
def clean_name(self):
return ' '.join(self.cleaned_data['name'].strip().split())
All item creates/updates are done via Django REST Framework:
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('id','name')
def create(self,validated_data):
item = Item.objects.create(name=validated_data['name'],client=self.context['request'].user.client)
item.save()
return item
I would prefer as much of the logic in the Model as possible (eg, not use SQL to create indexes), but could push some of the validation to the serializer if need be.
Tx.
I ended up with the following. The only caveat is that I have to include a name_slug field to store for sorting purposes.
models.py
class Item(BaseModel):
class Meta:
db_table = 'item'
ordering = ['name_slug']
# relations
client = models.ForeignKey(Client,related_name='items',null=True,blank=False)
# attributes
name = models.CharField(max_length=64, null=False, blank=False)
name_slug = models.CharField(max_length=64, null=False, blank=True)
def clean(self):
self.name = ' '.join(self.name.strip().split())
if Item.objects.filter(client=self.client,name__iexact=self.name).count() > 0:
raise ValidationError({'name': 'Name already exists. Please enter a different name'})
def save(self, *args, **kwargs):
self.name_slug = '-'.join(self.name.split()).lower()
super(Item, self).save(*args, **kwargs)
serializers.py
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('id','name','name_slug')
read_only_fields = ('name_slug',)
def validate(self, attrs):
attrs['client'] = self.context['request'].user.client
instance = Item(**attrs)
instance.clean()
return { k: getattr(instance,k) for k in attrs.keys()}

Django Rest Framework how to save a model with Related Field based on ID

I'm kind of new to DRF. I have Record model that looks like this:
class Records(models.Model):
owner = models.ForeignKey(User, null=True)
activity = models.ForeignKey(Activity, null=True)
time_start = models.DateTimeField(null=True)
time_end = models.DateTimeField(null=True)
...
The RecordSerializer is this one:
class RecordSerializer(serializers.ModelSerializer):
now = datetime.today()
owner = serializers.Field(source='owner.username')
time_start = serializers.DateTimeField(source='now')
class Meta:
model = Records
fields = ("owner", "activity", "time_start")
And this is the view:
class StartApiView(generics.CreateAPIView):
model = Records
serializer_class = RecordSerializer
def pre_save(self, obj):
obj.owner = self.request.user
The POST request is sent from Backbone and it includes a field with the activity id, for example "{activity:12}". What should I do if I want the view to save the Record and set the activity to the Activity with the id of 12?
The accepted answer was true for DRF v2.x but is no longer for newer (3.x) versions, as it would raise this AssertionError:
AssertionError: Relational field must provide a queryset argument, or set read_only=True.
For newer versions, just add the queryset argument to it:
class RecordSerializer(serializers.ModelSerializer):
activity = serializers.PrimaryKeyRelatedField(queryset=Activity.objects.all())
// [...]
Django REST Framework provides a PrimaryKeyRelatedField for exactly this use case.
class RecordSerializer(serializers.ModelSerializer):
activity = serializers.PrimaryKeyRelatedField()
owner = serializers.CharField(read_only=True, source='owner.username')
time_start = serializers.DateTimeField(source='now')
class Meta:
model = Records
fields = ("owner", "activity", "time_start")
This will produce output similar to what you are looking for, and it will accept the id of the activity when you want to update it.