Django serializer, nested relation and get_or_create - python

I've been bugging on this issue for some time now. I have two models : Acquisitions and RawDatas.
Each RawData have one Acquisition, but many RawDatas can have the same Acquisition.
I want to create or get the instance of Acquisition automatically when I create my RawDatas. And I want to be able to have all informations using the serializer.
class Acquisitions(models.Model):
class Meta:
unique_together = (('implant', 'beg_acq', 'duration_acq'),)
id = models.AutoField(primary_key=True)
implant = models.ForeignKey("Patients", on_delete=models.CASCADE)
beg_acq = models.DateTimeField("Beggining date of the acquisition")
duration_acq = models.DurationField("Duration of the acquisition")
class RawDatas(models.Model):
class Meta:
unique_together = (('acq', 'data_type'),)
id = models.AutoField(primary_key=True)
acq = models.ForeignKey("Acquisitions", on_delete=models.CASCADE)
data_type = models.CharField(max_length=3)
sampling_freq = models.PositiveIntegerField("Sampling frequency")
bin_file = models.FileField(db_index=True, upload_to='media')
And my serializers are these :
class AcquisitionSerializer(serializers.ModelSerializer):
class Meta:
model = Acquisitions
fields = ('id', 'implant', 'beg_acq', 'duration_acq')
class RawDatasSerializer(serializers.ModelSerializer):
acq = AcquisitionSerializer()
class Meta:
model = RawDatas
fields = ('id', 'data_type', 'sampling_freq', 'bin_file', 'acq')
def create(self, validated_data):
acq_data = validated_data.pop('acq')
acq = Acquisitions.objects.get_or_create(**acq_data)
RawDatas.objects.create(acq=acq[0], **validated_data)
return rawdatas
My problem is that, using this, if my instance of Acquisitions already exists, I get a non_field_errors or another constraint validation error.
I would like to know what is the correct way to handle this please ?
So I can automatically create this using the nested serializer, and when I only want to have informations (such as a GET request), I can have all the field I need (every field of the two models).
Thanks in advance for your help !

Try this:
class AcquisitionSerializer(serializers.ModelSerializer):
class Meta:
model = Acquisitions
fields = ('id', 'implant', 'beg_acq', 'duration_acq')
class RawDatasSerializer(serializers.ModelSerializer):
class Meta:
model = RawDatas
fields = ('id', 'data_type', 'sampling_freq', 'bin_file', 'acq')
def create(self, validated_data):
acq_data = validated_data.pop('acq')
acq = Acquisitions.objects.filter(id=acq_data.get('id')).first()
if not acq:
acq = AcquisitionSerializer.create(AcquisitionSerializer(), **acq_data)
rawdata = RawDatas.objects.create(acq=acq, **validated_data)
return rawdata

Related

Filter field of manytomany field by a list

I would like to do the following with Django REST Framework:
Filter results based on a field of a manytomany field.
The query would look like this:
https://endpoint.com/api/artwork/?having_style=Modern,Contemporary
I would expect the result to contain all ArtWork objects which contain a relation to a Style object with name "Modern", "Contemporary" or both.
The code below is not working and I don't know why.
models.py
class Style(models.Model):
name = models.CharField(validators=[validate_style], max_length=100, unique=True)
class ArtWork(models.Model):
styles = models.ManyToManyField(Style, default=None)
filters.py
class ArtWorkFilter(filters_rest.FilterSet):
having_style = django_filters.Filter(field_name="styles__name", lookup_expr='in')
class Meta:
model = ArtWork
fields = ['having_style']
class StyleSerializer(serializers.ModelSerializer):
class Meta:
model = Style
fields = ('name',)
class ArtWorkSerializer(serializers.ModelSerializer):
styles = StyleSerializer(many=True)
class Meta:
model = ArtWork
fields = ('styles'/)
views.py
class ArtWorkViewSet(viewsets.ModelViewSet):
permission_classes = []
queryset = ArtWork.objects.all()
serializer_class = ArtWorkSerializer
filter_backends = [filters_rest.DjangoFilterBackend,]
filterset_class= ArtWorkFilter
pagination_class = CursorSetPagination
Thank you in advance!
Solution
I solved it by changing the ArtWorkFilter to
filters.py
class ArtWorkFilter(filters_rest.FilterSet):
having_style = django_filters.Filter(field_name="styles__name", lookup_expr='in')
class Meta:
model = ArtWork
fields = ['having_style']
def filter_by_style_name(self, queryset, name, value):
list_styles = value.split(',')
return queryset.filter(styles__name__in=list_styles)
Try adding method param in Filter declaration. Something like:
class ArtWorkFilter(filters_rest.FilterSet):
having_style = django_filters.Filter(field_name="styles__name", lookup_expr='in')
class Meta:
model = ArtWork
fields = ['having_style']
def filter_by_style_name(self, queryset, name, value):
list_styles = value.split(',')
return queryset.filter(styles__name__in=list_styles)
CartItem.objects.filter(cart=cart, product=product, attribute__in=attribute_list).annotate(num_attr=Count('attribute')).filter(num_attr=len(attribute_list))

Nested Relationship in Django Doesn't Work

These are my models here:
class Site(models.Model):
siteID = models.CharField(max_length=255, primary_key=True)
class EndDevice(models.Model):
class Meta:
unique_together = ("edevID", "siteID")
edevID = models.CharField(max_length=255)
siteID = models.ForeignKey(Site, related_name='endDeviceList', on_delete=models.CASCADE)
deviceCategory = models.BigIntegerField()
This is my serilaizer:
class DeviceSerializer(serializers.ModelSerializer):
class Meta:
model = EndDevice
fields = ("edevID", "siteID", "deviceCategory")
class SiteSerializer(serializers.ModelSerializer):
endDeviceList = DeviceSerializer(many = True, read_only=True)
class Meta:
model = Site
fields = ("siteID", "endDeviceList")
This is my view:
class IndividualSite(generics.RetrieveUpdateDestroyAPIView):
'''
PUT site/{siteID}/
GET site/{siteID}/
DELETE site/{siteID}/
'''
queryset = EndDevice.objects.all()
serializer_class = SiteSerializer
I am trying to get/put/delete results using this class and I am trying to get all the EndDevice instances which have same siteID. But my serialzer only shows the siteID and doesn't show the endDeviceList (which should have the instants of the model EndDevice)
The problem is quite similar to this link:django rest-farmework nested relationships.
I have been trying different ways to serialize the objects, I think this is probably the smartest way, but have been really unsucccessful. Any help will be appreciated.
The urls.py:
urlpatterns = [
urlpatterns = [path('site/<str:pk>/', IndividualSite.as_view(), name = "get-site"),]
And it is connected to the main urls.
you are using read_only field for the Foreign relationship, remove that, as read_only wont display them
class SiteSerializer(serializers.ModelSerializer):
endDeviceList = DeviceSerializer(many = True)

Django Rest Framework - how to write multiple nested field serializer (for reading & writing)

I'm trying to write a "def create" method to perform nested serialization for multiple objects.
def create(self, validated_data):
suggested_songs_data = validated_data.pop('suggested_songs')
suggest_song_list = list()
for song_data in suggested_songs_data:
song = Song.objects.create(**song_data)
suggest_song_list.append(song)
message = Messages.objects.create(suggested_songs=suggest_song_list, **validated_data)
return message
Here is my schema:
class MessagesSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
suggested_songs = SongSerializer(many=True)
class Meta:
model = Messages
fields = ('id','owner','url','suggested_songs',)
#fields = ('id','url','suggested_songs',)
class SongSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Song
fields =('id','title','artist','album','albumId','num_votes','cleared')
read_only_fields = ('song_id')
But I am getting this error
Cannot assign "[<Song: Song object>, <Song: Song object>]": "Messages.suggested_songs" must be a "Song" instance.
Any advice?
EDIT:
Here is the model.
class Messages(models.Model):
owner = models.OneToOneField(User, primary_key=True, related_name='user_messages', editable=False) #TODO, change owner to 'To'
#suggested_songs = models.ForeignKey(Song, null=True, blank=True)
suggested_songs = models.ManyToManyField(Song, related_name='suggested_songs')
You can't create manyToMany relations without the objects already created. You must first create the objects and then make the relation.
Something like:
def create(self, validated_data):
suggested_songs_data = validated_data.pop('suggested_songs')
message = Messages.objects.create(**validated_data)
for song_data in suggested_songs_data:
song = Song.objects.create(**song_data)
message.suggested_songs.add(song)
return message

Include intermediary (through model) in responses in Django Rest Framework

I have a question about dealing with m2m / through models and their presentation in django rest framework. Let's take a classic example:
models.py:
from django.db import models
class Member(models.Model):
name = models.CharField(max_length = 20)
groups = models.ManyToManyField('Group', through = 'Membership')
class Group(models.Model):
name = models.CharField(max_length = 20)
class Membership(models.Model):
member = models.ForeignKey('Member')
group = models.ForeignKey('Group')
join_date = models.DateTimeField()
serializers.py:
imports...
class MemberSerializer(ModelSerializer):
class Meta:
model = Member
class GroupSerializer(ModelSerializer):
class Meta:
model = Group
views.py:
imports...
class MemberViewSet(ModelViewSet):
queryset = Member.objects.all()
serializer_class = MemberSerializer
class GroupViewSet(ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
When GETing an instance of Member, I successfully receive all of the member's fields and also its groups - however I only get the groups' details, without extra details that comes from the Membership model.
In other words I expect to receive:
{
'id' : 2,
'name' : 'some member',
'groups' : [
{
'id' : 55,
'name' : 'group 1'
'join_date' : 34151564
},
{
'id' : 56,
'name' : 'group 2'
'join_date' : 11200299
}
]
}
Note the join_date.
I have tried oh so many solutions, including of course Django Rest-Framework official page about it and no one seems to give a proper plain answer about it - what do I need to do to include these extra fields? I found it more straight-forward with django-tastypie but had some other problems and prefer rest-framework.
How about.....
On your MemberSerializer, define a field on it like:
groups = MembershipSerializer(source='membership_set', many=True)
and then on your membership serializer you can create this:
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.Field(source='group.id')
name = serializers.Field(source='group.name')
class Meta:
model = Membership
fields = ('id', 'name', 'join_date', )
That has the overall effect of creating a serialized value, groups, that has as its source the membership you want, and then it uses a custom serializer to pull out the bits you want to display.
EDIT: as commented by #bryanph, serializers.field was renamed to serializers.ReadOnlyField in DRF 3.0, so this should read:
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField(source='group.id')
name = serializers.ReadOnlyField(source='group.name')
class Meta:
model = Membership
fields = ('id', 'name', 'join_date', )
for any modern implementations
I was facing this problem and my solution (using DRF 3.6) was to use SerializerMethodField on the object and explicitly query the Membership table like so:
class MembershipSerializer(serializers.ModelSerializer):
"""Used as a nested serializer by MemberSerializer"""
class Meta:
model = Membership
fields = ('id','group','join_date')
class MemberSerializer(serializers.ModelSerializer):
groups = serializers.SerializerMethodField()
class Meta:
model = Member
fields = ('id','name','groups')
def get_groups(self, obj):
"obj is a Member instance. Returns list of dicts"""
qset = Membership.objects.filter(member=obj)
return [MembershipSerializer(m).data for m in qset]
This will return a list of dicts for the groups key where each dict is serialized from the MembershipSerializer. To make it writable, you can define your own create/update method inside the MemberSerializer where you iterate over the input data and explicitly create or update Membership model instances.
I just had the same problem and I ended it up solving it with an annotation on the group queryset.
from django.db.models import F
class MemberSerializer(ModelSerializer):
groups = serializers.SerializerMethodField()
class Meta:
model = Member
def get_groups(self, instance):
groups = instance.groups.all().annotate(join_date=F(membership__join_date))
return GroupSerializer(groups, many=True).data
class GroupSerializer(ModelSerializer):
join_date = serializers.CharField(required=False) # so the serializer still works without annotation
class Meta:
model = Group
fields = ..., 'join_date']
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.
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
You would technically, have to pass the Request to DataAccessLayer which would return the Filtered Objects from Data Access Layer but as I have to Answer the Question in a Fast Manner so i adjusted the Code in Business Logic Layer!

How do I filter values in a Django form using ModelForm?

I am trying to use the ModelForm to add my data. It is working well, except that the ForeignKey dropdown list is showing all values and I only want it to display the values that a pertinent for the logged in user.
Here is my model for ExcludedDate, the record I want to add:
class ExcludedDate(models.Model):
date = models.DateTimeField()
reason = models.CharField(max_length=50)
user = models.ForeignKey(User)
category = models.ForeignKey(Category)
recurring = models.ForeignKey(RecurringExclusion)
def __unicode__(self):
return self.reason
Here is the model for the category, which is the table containing the relationship that I'd like to limit by user:
class Category(models.Model):
name = models.CharField(max_length=50)
user = models.ForeignKey(User, unique=False)
def __unicode__(self):
return self.name
And finally, the form code:
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
How do I get the form to display only the subset of categories where category.user equals the logged in user?
You can customize your form in init
class ExcludedDateForm(ModelForm):
class Meta:
model = models.ExcludedDate
exclude = ('user', 'recurring',)
def __init__(self, user=None, **kwargs):
super(ExcludedDateForm, self).__init__(**kwargs)
if user:
self.fields['category'].queryset = models.Category.objects.filter(user=user)
And in views, when constructing your form, besides the standard form params, you'll specify also the current user:
form = ExcludedDateForm(user=request.user)
Here example:
models.py
class someData(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
class testKey(models.Model):
name = models.CharField(max_length=100,verbose_name="some value")
tst = models.ForeignKey(someData)
class testForm(forms.ModelForm):
class Meta:
model = testKey
views.py
...
....
....
mform = testForm()
mform.fields["tst"] = models.forms.ModelMultipleChoiceField(queryset=someData.objects.filter(name__icontains="1"))
...
...
Or u can try something like this:
class testForm(forms.ModelForm):
class Meta:
model = testKey
def __init__(self,*args,**kwargs):
super (testForm,self ).__init__(*args,**kwargs)
self.fields['tst'].queryset = someData.objects.filter(name__icontains="1")
I know this is old; but its one of the first Google search results so I thought I would add how I found to do it.
class CustomModelFilter(forms.ModelChoiceField):
def label_from_instance(self, obj):
return "%s %s" % (obj.column1, obj.column2)
class CustomForm(ModelForm):
model_to_filter = CustomModelFilter(queryset=CustomModel.objects.filter(active=1))
class Meta:
model = CustomModel
fields = ['model_to_filter', 'field1', 'field2']
Where 'model_to_filter' is a ForiegnKey of the "CustomModel" model
Why I like this method:
in the "CustomModelFilter" you can also change the default way that the Model object is displayed in the ChoiceField that is created, as I've done above.
is the best answer:
BookDemoForm.base_fields['location'] = forms.ModelChoiceField(widget=forms.Select(attrs={'class': 'form-control select2'}),queryset=Location.objects.filter(location_for__fuel=True))

Categories