I'm trying to make a demo website for job applications using Angular 6 and Django Rest Framework. One of my application fields involve listing your interests in a chip input field like this one.
The JSON being sent to my API would look something like this:
{
...
'firstname': 'Firstname',
'lastname': 'Lastname'
...
'interests': ['hobby1', 'hobby2', 'hobby3', 'hobby4'],
...
}
Now as far as I know Django REST Framework supplies a serializer field that is written something like this:
interests = serializers.ListField(
item = serializers.CharField(min_value=xx, max_value=xx)
)
My question is, how do I proceed from here? What model fields do I use, or do I have to create my own save function that iterates through the interests and saves every single one?
Many to many relationship is the thing you are looking for.
You can even have nested serializer so the output of the parent objects would include the serialized interests in the JSON.
class ParentSerializer(serializers.ModelSerializer):
child_set = ChildSerializer(many=True)
class Meta:
depth = 1
model = Parent
fields = ('id', 'other_atributes', 'child_set')
Also you can easily edit those relationship in Django Admin, can post a snippet of that also if you would be interested.
'interests': ['hobby1', 'hobby2', 'hobby3', 'hobby4']
This is basically valid JSON, so you can parse this easily on your end.
Related
I'm exposing an REST api for legacy application.
I have a Company model class that defines the following fields:
address_street (required)
address_street_number (required)
shipping_address_street (optional)
shipping_address_street_number (optional)
billing_address_street (optional)
... you got the point
I would like to group all address fields into an Adress serializer in order to have a cleaner structure.
Some thing like:
{
"adress": {"street": "foo", "street_number": "bar"},
"shipping_address": {"street": "ham", "street_number": "spam"},
"billing_address": null,
}
So far, I can create a CompanySerializer from a rest_framework.serializers.Serializer and manually build my Company objects from this.
It is tedious, but it will work.
But how can I build a rest_framework.serializers.ModelSerializer for my Company model, changing the way fields are structured to have my model fields automatically populated by rest framework ?
DRF nested model serializers seems to only work for relations, not groups of fields.
Am I to build my models instances by hands or is there a way to decouple the representation of a model serializer to my object ?
From ModelSerializer documentation
The process of automatically determining a set of serializer fields
based on the model fields is reasonably complex
You probably should stick to the "tedious" method you mention (you will have to put in some effort if serializer representation and model fields have different structures altogether).
ModelSerializer is tightly linked to the model in question, so overriding that behaviour seems to be for little benefit when you can do the same thing using a plain Serializer and put object creation under save.
Maybe you need to override the data property/method on the Serializer subclass so that you get a dict that is fit for consumption directly by the model, that might make it less tedious
You can build custom serialiser fields with SerializerMethodField:
from rest_framework.fields import SerializerMethodField
class AdressSerializer(ModelSerializer):
adress = SerializerMethodField()
shipping_address = SerializerMethodField()
def get_adress(self, instance):
return {
"street": instance.address_street,
"street_number": instance.address_street_number
}
def get_shipping_address(self, instance):
// same approach as above
If needed to populate the model from the same data representation, the best approach is to override serialiser's save method. I don't think there is an "automatic" way of doing it.
Another Django REST Framework problem that I don't understand how to solve?
There are two objects image_1 and image_2.
In serializers.py:
class someClass(serializers.ModelSerializer):
image_1 = serializers.ImageField(source='image_1')
image_2 = serializers.ImageField(source='image_2')
class Meta:
model = onlyimage
fields = ['image_1', 'image_2']
In the output, I get:
<image_1>https://domain-name.com/media/image_1</image_1>
<image_2>https://domain-name.com/media/image_2</image_2>
I want the tag not to be numbered like in the example below:
<image>https://domain-name.com/media/image_1</image>
<image>https://domain-name.com/media/image_2</image>
But if you change in serializers.py, of course, an error occurs:
class someClass(serializers.ModelSerializer):
image = serializers.ImageField(source='image_1')
image = serializers.ImageField(source='image_2')
class Meta:
model = onlyimage
fields = ['image', 'image']
It appears you are trying to use a serializer to generate HTML, so the short answer is you can't use serialization in that way.
The documentation says:
Serializers allow complex data such as querysets and model instances
to be converted to native Python datatypes that can then be easily
rendered into JSON, XML or other content types.
Although it says "... or other content types." the use of serialization is to convert your model in to a simple data stream for transmission and then, importantly, deserialization.
By definition fields in a model must be unique. If your serialized model has two fields both called <image> the deserialization process will not know which bit of you data to put where.
Without more information on what you are actually trying to do with your model it's hard to suggest another way.
For my project I started to use Laravel for the api than I switched to Django / django rest framework, I did this to gain more speed as I need to query large data.
Now I got the following situation:
I got a "Group" which has "Subjects" and which has a recursive relation.
Now a group can have like 2000+ subjects(including the descendent subjects) a parent subject has +/- 30 subjects.
This is my code:
serializers
class RecursiveField(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data
class SubjectSerializer(serializers.ModelSerializer):
parent_of = RecursiveField(many=True, read_only=True)
class Meta:
model = Subject
fields = ("id", "name", "parent_of", "parent")
class GroupSerializer(serializers.ModelSerializer):
subjects = SubjectSerializer(many=True, read_only=True)
class Meta:
model = Group
fields = ("id", "name", "subjects")
def setup_eager_loading(cls, queryset):
return queryset.prefetch_related("subjects")
views
class GroupViewSet(ModelViewSet):
class Paginator(BasePaginator):
model = Group
queryset = Group.objects.all()
serializer_class = serializers.GroupSerializer
pagination_class = Paginator
def get_queryset(self):
return self.get_serializer().setup_eager_loading(GroupViewSet.queryset)
I tested the same request with the laravel api and its much faster, still noticeable slow but its ok(5-10 secs). With django rest framework its too slow (1 minute +/-), and thats just a page with 1 group that has 2500 subjects.
I do know what takes long, the RecursiveField class, because when I remove that the query is done in less than 2 seconds. So my question is whats the main cause, because it's creates a recursive relation (I doubt)? Or is it because I don't prefetch?
And ofcourse whats the best way to do this?
Thank you
You have a few options, but I don't think any are great. Recursive queries aren't very well supported with Django.
Rework your data model to prevent needing to use recursion to fetch the subjects from the database. You could add a root ForeignKey to Subject on Subject that would identify the root subject. This would allow you to grab all subjects in a tree fairly easily. Then you'd have to arrange them in your View/Viewset to fit the ordering (if that's necessary).
Use raw() and your database's recursive functionality to fetch the models. This would require raw SQL and can be painful to maintain.
Use django_cte. I've used this in one of my projects for a few queries, but I'm not a big fan of it. It breaks some functionality with update() being called on an empty queryset. However, it will work and it won't require you to drop down to raw SQL.
The problem is not DRF, but the data structure itself.
It is very slow in django to query all ancestors/descendants recursively, your should use a more efficient data structure.
For the same reason I wrote django-treenode, it performs tree operations without query the db.
You can read the docs here: https://github.com/fabiocaccamo/django-treenode
I have large table of data (~30 Mb) that I converted into into a model in Django. Now I want to have access to that data through a REST API.
I've successfully installed the Django REST framework, but I'm looking for a way to automatically create a URL for each field in my model. My model has about 100 fields, and each field has about 100,000 entries.
If my model is named Sample,
models.py
class Sample(models.Model):
index = models.IntegerField(primary_key=True)
year = models.IntegerField(blank=True, null=True)
name = models.TextField(blank=True, null=True)
...97 more fields...
then I can access the whole model using Django REST framework like this:
urls.py
class SampleSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Sample
fields = ( **100 fields**)
class SampleViewSet(viewsets.ModelViewSet):
queryset = Sample.objects.all()
serializer_class = SampleSerializer
router = routers.DefaultRouter()
router.register(r'sample', SampleViewSet)
But of course my browser can't load all of that data in a reasonable amount of time. I could manually make a different class and URL for each field, but there must be a better way... I want to be able to go to my_site.com/sample/year (for example) and have it list all of the years in JSON format, or my_site.com/sample/name and list all the names, etc.
Please help me figure out how to do this, thanks!
You might be able to do that using a custom viewset route.
You have this:
class ModelViewSet(ModelViewSet):
#list_route()
def sample_field(self, request):
desired_field = request.data.get('field', None)
if not desired_field:
return response # pseudocode
values = Model.objects.all().values_list(desired_field, flat=True)
# serialize this for returning the response
return Response(json.dumps(values)) # this is an example, you might want to do something mode involved
You will be able to get this from the url:
/api/model/sample_field/?field=foo
This extra method on the viewset will create a new endpoint under the samples endpoint. Since it's a list_route, you can reach it using /sample_field.
So following your code, it would be:
mysite.com/sample/sample_field/?field='year'
for example.
There are many interesting details in your question, but with this sample I think you might able to achieve what you want.
Try to use pagination. You can do it in almost the same way as in you question. Pagination in django lets you divide the results into pages. You don't have to display all the entries in the same page. I think this is the best option for you.
Refer django documentation on pagination:
Pagination in django
I have an angular app that presents a form with contact data and a list of contact numbers. Each contact can have N numbers and each of those numbers has a type (cell,home,work...) The code below will send the json to the angular app just fine and I can deal with it there including adding new numbers, removing numbers ..... However when DRF gets the exact same format json back, it can't deserialize it. It throws off this error:
AttributeError: 'Contact' object has no attribute 'numbers'
which is totally valid, but the serializer DOES have that field and should be able to hang onto those values so I can save them after I save the contact.
If I do something totally hokey like this in the update method:
self.object = self.get_object_or_none()
self.object.numbers = []
I can eliminate that error, but then it throws off these kind of errors:
{'numbers': [
{u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.']},
{u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.']},
{u'non_field_errors': [u'Cannot create a new item, only existing items may be updated.']}
]}
The first two phone numbers aren't new, they have id fields and came from the db, the third one is new, I'm trying to add it.
Here is the code. Surely this isn't that bizarre a way to do things. Is Django Rest Framework what I should be using? I keep running into show stoppers like this that seem to be the documented way to do things, but then they blow up spectacularly.
class PhoneTypeSerializer(serializers.ModelSerializer):
class Meta:
model = PhoneType
class ContactPhoneSerializer(serializers.ModelSerializer):
number_type = PhoneTypeSerializer(source='number_type')
class Meta:
model = ContactPhone
depth = 1
exclude = ('owner',)
class ContactSerializer(serializers.ModelSerializer):
numbers = ContactPhoneSerializer(source='number_set', many=True, required=False)
class Meta:
model = Contact
How do I deserialize this data so I can save it?
I had the same issue and solved it by adding to the serializer some flags. In your case it should be something like:
number_type = PhoneTypeSerializer(source='number_type', many=True, required=False, allow_add_remove=True, read_only=False)
A bit late, but maybe it still helps. Found this suggestion here:
Updating m2m not possible when using serializers as fields