Lets say I have three models Submission, Contact and SubmissionContact.
class Submission(models.Model):
title = models.CharField(max_length=255, verbose_name='Title')
...
class Contact(models.Model):
name = models.CharField(max_length=200, verbose_name='Contact Name')
email = models.CharField(max_length=80, verbose_name='Contact Email')
...
class SubmissionContact(models.Model):
submission = models.ForeignKey(Submission)
contact = models.Foreign(Contact, verbose_name='Contact(s)')
Can I read / write to all these three tables using a single ModelResource using tastypie. (basically get and put actions on list and detail in tastypie)
Thanks in advance for any help.
You can nest one model into the other or use the dehydrate cycle to add extra resources to your output, for example consider a Foo and Bar model
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all()
resource_name = 'foo'
serializer = Serializer(formats=['xml', 'json'])
excludes = ['date_created']
class BarResource(ModelResource):
foo = fields.ForeignKey(FooResource, attribute='foo', full=True, null=True)
class Meta:
queryset = Bar.objects.all()
resource_name = 'bar'
serializer = Serializer(formats=['xml', 'json'])
If there's no relationship you could also do something like (with large datasets this would cause a lot of database overhead, you might have to rethink your model definitions):
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all()
resource_name = 'foo'
serializer = Serializer(formats=['xml', 'json'])
excludes = ['date_created']
def dehydrate(self, bundle):
obj = self.obj_get(id=bundle.data['id'])
bundle.data['bar'] = Bar.objects.get(id=1).name
return bundle
Related
I want to calculate the average rating by using SerializerMethodField().
The error in the following code is AttributeError: 'FeedbackModel' object has no attribute 'aggregate'
I think _set is missing but I don't know where to put it..!
class FeedbackSerializer(serializers.ModelSerializer):
feedback_by_user_profile_pic = serializers.ImageField(source='feedback_by.profile_pic')
average_rating = serializers.SerializerMethodField()
def get_average_rating(self,instance):
return instance.aggregate(average_rating=Avg('rating'))['average_rating']
class Meta:
model = FeedbackModel
fields = ['feedback_text','rating','date','feedback_by_user_profile_pic','average_rating']
Feedback Model
class FeedbackModel(models.Model):
feedback_text = models.CharField(max_length=1000)
rating = models.IntegerField()
date = models.DateField(auto_now=True)
feedback_by = models.ForeignKey(UserModel,on_delete=models.CASCADE)
business_account = models.ForeignKey(BusinessAccountModel,on_delete=models.CASCADE)
class Meta:
db_table = 'feedback'
BusinessAccountModel
class BusinessAccountModel(models.Model):
business_title = models.CharField(max_length=70)
business_description = models.CharField(max_length=500)
status = models.CharField(max_length=100)
note = models.CharField(max_length=200)
user = models.OneToOneField(UserModel,on_delete=models.CASCADE)
class Meta:
db_table = 'wp_business_acc'
BusiAccSerializer
class BusiAccSerializer(serializers.ModelSerializer):
class Meta:
model = BusinessAccountModel
fields = '__all__'
I think you need to add the average_rating field in the BusiAccSerializer, not in the FeedbackSerializer.
First you have to set the related_name attribute in the FeedbackModel.
class FeedbackModel(models.Model):
...
# here I added the `related_name` attribute
business_account = models.ForeignKey(BusinessAccountModel,on_delete=models.CASCADE, related_name="feedbacks")
And then in the BusiAccSerializer,
class BusiAccSerializer(serializers.ModelSerializer):
average_rating = serializers.SerializerMethodField(read_only = True)
def get_average_rating(self, obj):
return obj.feedbacks.aggregate(average_rating = Avg('rating'))['average_rating']
class Meta:
model = BusinessAccountModel
fields = (
'business_title', 'business_description', 'status', 'note', 'user', 'average_rating',
)
First of all, you've indicated that you need an average rating for a business account, but you can not get an average rating for an account without having a concrete business account, so you need to do it in the business account serializer.
David Lu has already answered how to do it in the BusiAccSerializer, but I have something to add:
What you've trying to do is to use a serializer method field to add some aggregated data to the output. This way of solving your problem has a major drawback: when you will try to serialize a list of BusinessAccountModels, the serializer will do a separate database call for each business account and it could be slow. You better need to specify an annotated queryset in your view like this:
BusinessAccountModel.objects.all().annotate(average_rating=Avg('feedbacks__rating'))
Then you will be able to use the result of calculation as a regular field in your serializer:
class BusiAccSerializer(serializers.ModelSerializer):
...
average_rating = serializers.FloatField(read_only=True)
This way there will be no additional database queries done by the serializer.
I want to make a common filter to my models because I need to filter all my objects to return a gap between time_start and time_end, but apparently it doesn't work.
I'm not sure if it's even possible(But I hope so, because it won't by DRY otherwise).
models.py
class Time(models.Model):
time = models.TimeField()
class Meta:
abstract=True
class Mark(Time):
value = models.IntegerField(verbose_name="mark")
teacher = models.CharField(max_length=20)
subject = models.CharField(max_length=20)
serializers.py
class MarkSerializer(serializers.ModelSerializer):
class Meta:
model = Mark
fields = ('id', 'time','value', 'teacher', 'subject')
filers.py
class DataFilter(django_filters.FilterSet):
start_time = django_filters.TimeFilter(name="time", lookup_expr='gte')
end_time = django_filters.TimeFilter(name="time", lookup_expr='lte')
class Meta:
model = Time
fields = ['start_time', 'end_time']
views.py
class MarkViewSet(viewsets.ModelViewSet):
serializer_class = MarkSerializer
queryset = Mark.objects.all()
filter_class = DataFilter
I try to get needed marks through:
127.0.0.1:8000/api/v0/marks/?time_start=11:40:00&time_end=12:00:00
but it returns all the objects that I have not the filtered ones.
Thanks in advance.
You have passed the filter params wrong, it should be the name of the field you described in the filter class DataFilter.
Hit this endpoint in the browser,
127.0.0.1:8000/api/v0/marks/?start_time=11:40:00&end_time=12:00:00
I am trying to use nested serializer. How do I use the root serializer to filter data on the grandchild serializer?
School and Program have a many to many relationship So that any school can subscribe to any program. Each school has classes and those classes are part of a program, that's why PClass has foreign keys to both School and program.
When I call my api .../api/school/1 I want to get all the programs that school subscribes to and which classes are available in each program (in that school)
class School(TimeStampedModel, SoftDeletableModel):
name = models.CharField(max_length=40)
slug = models.SlugField(max_length=40, default='', blank=True)
class Program(TimeStampedModel, SoftDeletableModel):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=50,default='',blank=True, unique=True)
description = models.CharField(max_length=100, blank=True)
school = models.ForeignKey(School, blank=True, null=True, related_name="programs")
class PClass(TimeStampedModel, SoftDeletableModel):
name = models.CharField(max_length=50)
slug = models.SlugField(max_length=50,default='',blank=True)
description = models.CharField(max_length=100)
program = models.ForeignKey(Program, related_name="classes")
school = models.ForeignKey(School, related_name="classes")
and the following serializers:
class SchoolSerializer( serializers.ModelSerializer):
programs = ProgramSerializer(source='get_programas',many=True,read_only=True)
class Meta:
model = School
fields = '__all__'
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
class PClassSerializer(serializers.ModelSerializer):
class Meta:
model = Class
fields = ('name','slug')
class ProgramSerializer(serializers.ModelSerializer):
school = serializers.SlugRelatedField(queryset=School.objects.all(),
slug_field='name',
required=False)
classes = PClassSerializer(many=True,read_only=True)
class Meta:
model = Program
exclude = ('id',)
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
is this possible? or is it a problem with the way I set up my models?
There's 2 ways I know how to do this. The first is you're pretty close already
EDIT: Noticed you're using related names. I've updated the answer for that
class SchoolSerializer( serializers.ModelSerializer):
programas = ProgramSerializer(source='programs',many=True,read_only=True)
For more complex filtering the best way is to use a SerializerMethodField Field. Here's an example.
You'll probably want to also do some pre-fetches in your view to get the queryset to minimize the # of queries.
class SchoolSerializer(serializers.ModelSerializer):
programas = SerializerMethodField(source='get_programas',many=True,read_only=True)
class Meta:
model = Unidade
fields = '__all__'
lookup_field = 'slug'
extra_kwargs = {
'url': {'lookup_field': 'slug'}
}
def get_programas(self, obj):
# You can do more complex filtering stuff here.
return ProgramaSerializer(obj.programs.all(), many=True, read_only=True).data
To get the PClasses you'll just need to filter your queryset with
program.classes.filter(school=program.school)
Full example for ProgramSerializer is
class ProgramSerializer(serializers.ModelSerializer):
classes = SerializerMethodField(source='get_classes', many=True, read_only=True)
class Meta:
model = Program
def get_classes(self, obj):
return PClassSerializer(obj.classes.filter(school=obj.school), many=True, read_only=True).data
EDIT 10 or so:
Since you have changed the program -> School from foreignkey to ManyToMany, this changes everything.
For the schoolserializer, you need to use a SerializerMethodField. This way you can pass in extra context to your nested serializer.
class SchoolSerializer(serializers.ModelSerializer):
classes = SerializerMethodField(source='get_programs')
class Meta:
model = School
def get_programs(self, obj):
return ProgramSerializer(obj.program_set.all(), many=True, read_only=True, context={ "school": obj }).data
class ProgramSerializer(serializers.ModelSerializer):
classes = SerializerMethodField(source='get_classes', many=True, read_only=True)
class Meta:
model = Program
def get_classes(self, obj):
return PClassSerializer(obj.classes.filter(school=self.context["school"]), many=True, read_only=True).data
I have a class that has multiple references to the same generic class for different information:
class Resort(models.Model):
id = models.PositiveIntegerField(_('HapNr.'), primary_key=True)
symbol = models.CharField(_('Kurzzeichen'), max_length=3, blank=True)
short_description = GenericRelation('MultiLingualText',
verbose_name=_('Beschreibung (ML)'),
related_query_name='resortshortdescriptions')
long_description = GenericRelation('MultiLingualText',
verbose_name=_('Einleitung (ML)'),
related_query_name='resortlongdescription')
class MultiLingualText(models.Model):
language = models.ForeignKey(hmodels.LanguageCode, verbose_name=_('Sprache'))
valid_from = models.DateField(_('Gültig ab'), default=timezone.now)
text = models.TextField(_('Text'))
content_type = models.ForeignKey(ContentType, verbose_name=_('Typ'))
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
atu_id = models.CharField(_('ATU Text Id'), max_length=12, editable=False, blank=True)
atu_context = models.CharField(_('ATU Kontext'), max_length=1, editable=False, blank=True)
When I need to work with this class using the Django Admin I have two Inlines, each with a queryset selecting the correct texts for that relation. This works fine.
I tried doing something similar by using individual serializers and viewsets for each relation, but when I retrieve a resort, it still shows all texts with each relation.
class ResortSerializer(serializers.HyperlinkedModelSerializer):
short_description = MultiLingualTextSerializerRSD(many=True, read_only=True)
long_description = MultiLingualTextSerializerRLD(many=True, read_only=True)
class Meta:
model = Resort
class MultiLingualTextSerializerRSD(serializers.HyperlinkedModelSerializer):
language = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = MultiLingualText
class MultiLingualTextViewSetRSD(viewsets.ModelViewSet):
serializer_class = MultiLingualTextSerializerRSD
queryset = MultiLingualText.objects.exclude(atu_id='').order_by('resort', 'content_type', 'object_id',
'-valid_from')
class ResortViewSet(viewsets.ModelViewSet):
queryset = Resort.objects.all().order_by('id')
serializer_class = ResortSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('id', 'sihot_nr')
So basically my question is, how can I use different querysets for each set of texts? Or is this at all possible?
Correct implementation (Thanks to #lucasnadalutti)
class ResortSerializer(serializers.HyperlinkedModelSerializer):
short_description = serializers.SerializerMethodField()
long_description = serializers.SerializerMethodField()
def get_short_description(self, obj):
qs = MultiLingualText.objects.exclude(atu_id='').order_by('-valid_from', 'language__code')
return MultiLingualTextSerializer(qs, many=True, read_only=True).data
def get_long_description(self, obj):
qs = MultiLingualText.objects.filter(atu_id='').order_by('-valid_from', 'language__code')
return MultiLingualTextSerializer(qs, many=True, read_only=True).data
MultiLingualTextViewSetRSD does not make much sense in this context, as what you want is to send the resorts and their descriptions in one request only, as it should be. In ordinary ForeignKey model field relations, I'm pretty sure that ResortSerializer would serialize only its related records as you expected, but I'm not sure about how DRF serializers work with generic relations.
That said, one solution would be to replace:
short_description = MultiLingualTextSerializerRSD(many=True, read_only=True)
long_description = MultiLingualTextSerializerRLD(many=True, read_only=True)
with:
short_description = SerializerMethodField()
long_description = SerializerMethodField()
And implement your filtering-and-serialization inside get_short_description and get_long_description methods. Another solution is to remove both attributes and place this logic inside your serializer's to_representation method.
I am new to Tastypie (and Django) and am running into a problem with circular many-to-many relationships in my app's api.
I have 3 models RiderProfile, Ride, and RideMemebership. RiderProfilea can belong to multiple Rides, and Rides can have multiple RiderProfile. The many-to-many relationship is mediated by RideMembership. My models look like:
class RiderProfile(models.Model):
user = models.OneToOneField(User)
age = models.IntegerField(max_length=2)
rides = models.ManyToManyField('riderapp.Ride', through="RideMembership")
def __unicode__(self):
return self.user.get_username()
class Ride(models.Model):
name = models.CharField(max_length=64)
riders = models.ManyToManyField(RiderProfile, through="RideMembership")
def __unicode__(self):
return self.name
class RideMembership(models.Model):
rider = models.ForeignKey(RiderProfile)
ride = models.ForeignKey(Ride)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
def __unicode__(self):
return self.rider.user.get_username() + ' to ' + self.ride.name()
My TastyPie resources look like:
class UserResource(ModelResource):
...
class RideResource(ModelResource):
class Meta:
queryset = Ride.objects.all()
resource_name = 'rides'
riders = fields.ToManyField('riderapp.api.RiderProfileResource', 'riders', full=True)
class RiderProfileResource(ModelResource):
class Meta:
queryset = RiderProfile.objects.all()
resource_name = 'riders'
user = fields.ForeignKey(UserResource, 'user', full=True)
rides = fields.ToManyField('riderapp.api.RideResource', 'rides', full=True)
When I GET either a RiderProfile or Ride (list or detail), I get a recursion error because the models are fetching themselves infinitely. I have tried using the RelationalField.use_in parameter, which is very close to what I am trying to accomplish - as it prevents a field from being included based whether the request is for a list or a detail. However, I am trying to remove a resource field based on which endpoint is called.
For instance, a request for /rides:
I would like to have a list of all the RiderProfile items involved, but without their Ride list.
Likewise, a request for /riders:
I would like to have a list of all the Ride items for the RiderProfile, but without their Rider list.
What is the recommended solution for this? I have been playing the with the dehyrdate cycle, but am struggling to modify the set of related resources. I have also read answers about using multiple ModelResources for Rides and Riders. Is there a recommended way to accomplish this?
Thanks in advance for your advice!
Update
I added extra ModelResources for use with each endpoint (RiderProfileForRideResource and RideForRiderProfileResource), and it is working. I am just not sure this is the best approach. It creates additional endpoints that I don't really want to expose. Any thoughts on a better way?
class UserResource(ModelResource):
...
class RideResource(ModelResource):
class Meta:
queryset = Ride.objects.all()
resource_name = 'rides'
riders = fields.ToManyField('riderapp.api.RiderProfileForRideResource', 'riders', full=True)
class RideForRiderProfileResource(ModelResource):
class Meta:
queryset = Ride.objects.all()
resource_name = 'rides_for_riders'
class RiderProfileResource(ModelResource):
class Meta:
queryset = RiderProfile.objects.all()
resource_name = 'riders'
user = fields.ForeignKey(UserResource, 'user', full=True)
rides = fields.ToManyField('riderapp.api.RideForRiderProfileResource', 'rides', full=True)
class RiderProfileForRideResource(ModelResource):
class Meta:
queryset = RiderProfile.objects.all()
resource_name = 'riders_for_ride'
user = fields.ForeignKey(UserResource, 'user', full=True)
class RideMembershipResource(ModelResource):
class Meta:
queryset = RideMembership.objects.all()
resource_name = 'rider_membership'
This might not be the cleanest one way to do it but you could try to remove riders or rides in the dehydrate cycle by checking the resource uri path of the api call you have made
class RideResource(ModelResource):
class Meta:
queryset = Ride.objects.all()
resource_name = 'rides'
riders = fields.ToManyField('riderapp.api.RiderProfileResource', 'riders', full=True)
def dehydrate(self, bundle):
# You make api call to 'riders' and are dehydrating related source RideResource. Path should be of the form API/app_name/riders
# When call made directly to this resource then uri path will be API/app_name/rides and if statement will not be entered
if 'riders' in bundle.request.path:
del bundle.data['riders']
and vice versa for the opposite relation.
You can use a callable for the use_in attribute of your resource field instead of overriding dehydrate.
def riders_check(bundle):
return 'riders' in bundle.request.path
Something like,
riders = fields.ToManyField('riderapp.api.RiderProfileForRideResource', 'riders', full=True, use_in=riders_check)