Django REST Framework Serializing models that have fields / relationships ForeignKey and ManyToManyFields

4

The challenge of this question is clear from the update 2 which is the current state of it, you can read it full to understand its context if you want

:)

I have the following model that I want to serialize to expose via REST

class RehabilitationSession(models.Model):

    patient = models.ForeignKey('userprofiles.PatientProfile', null=True, blank=True,verbose_name='Paciente', related_name='patientprofile')

    slug = models.SlugField(max_length=100, blank=True)

    medical = models.ForeignKey('userprofiles.MedicalProfile', null=True, blank=True,
                            verbose_name='Médico tratante')
    therapist = models.ForeignKey('userprofiles.TherapistProfile', null=True, blank=True, verbose_name='Terapeuta')


    date_session_begin = models.DateTimeField(default=timezone.now(), verbose_name = 'Fecha de inicio')

    upper_extremity = MultiSelectField(
    max_length=255,
    choices=EXTREMITY_CHOICES,
    blank=False,
    verbose_name='Extremidad Superior'
)

    affected_segment = models.ManyToManyField(AffectedSegment,verbose_name='Segmento afectado')
    movement = ChainedManyToManyField(
    Movement, #Modelo encadenado
    chained_field = 'affected_segment',
    chained_model_field = 'corporal_segment_associated',
    verbose_name='Movimiento'
)

    metrics = models.ManyToManyField(Metric, blank=True, verbose_name='Métrica')
    date_session_end = models.DateTimeField(default=timezone.now(), verbose_name = 'Fecha de finalización')
    period = models.CharField(max_length=25,blank=True, verbose_name='Tiempo de duración de la sesión')

    class Meta:
        verbose_name = 'Sesiones de Rehabilitación'

    def __str__(self):
        return "%s" % self.patient

To serialize the fields that are Foreingkey, I am basing myself on this REST Framework documentation .

My serializers.py file is this:

from .models import RehabilitationSession
from rest_framework import serializers

class RehabilitationSessionSerializer(serializers.HyperlinkedModelSerializer):

patient = serializers.HyperlinkedIdentityField(view_name='patientprofile',)

class Meta:
    model = RehabilitationSession
    fields = ('url','id','patient',
              'date_session_begin','status','upper_extremity',

              'date_session_end', 'period','games','game_levels',
              'iterations','observations',)

I am using HyperlinkedIdentityField because my model is serialized with HyperlinkedModelSerializer, but It is not clear to me how I should serialize it according to the options that are given there. It says that HyperlinkedIdentityField can also be used on an attribute of the object, and the idea is that in the view of my api when the model is serialized, show me the url of that field, which is an instance of the PatientProfile model, is say a record of a patient.

UPDATE

My main file urls.py where I include the routes to determine the urls is:

from django.conf.urls import url, include #patterns
from django.contrib import admin

from .views import home, home_files

# REST Framework packages
from rest_framework import routers
from userprofiles.views import UserViewSet, GroupViewSet, PatientProfileViewSet
from medical_encounter_information.views import RehabilitationSessionViewSet

router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet)
router.register(r'rehabilitation-session', RehabilitationSessionViewSet)
router.register(r'patientprofile', PatientProfileViewSet)

urlpatterns = [
    url(r'^admin/', admin.site.urls),

    url(r'^chaining/', include('smart_selects.urls')),

    url(r'^$', home, name='home'),

    url(r'^', include('userprofiles.urls')),
    #Call the userprofiles/urls.py

    url(r'^', include('medical_encounter_information.urls' )),
    #Call the medical_encounter_information/urls.py

    #  which is a regular expression that takes the desired urls and passes as an argument
    # the filename, i.e. robots.txt or humans.txt.
    url(r'^(?P<filename>(robots.txt)|(humans.txt))$',
        home_files, name='home-files'),

    #REST Frameworks url's
    # Wire up our API using automatic URL routing.
    # Additionally, we include login URLs for the browsable API.

    url(r'^api/', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

]

When I try to access the url of my api rest of said model, I get the following message in my console:

    File "/home/bgarcial/.virtualenvs/neurorehabilitation_projects_dev/lib/python3.4/site-packages/rest_framework/relations.py", line 355, in to_representation
    raise ImproperlyConfigured(msg % self.view_name)
django.core.exceptions.ImproperlyConfigured: Could not resolve URL for hyperlinked relationship using view name "patientprofile". You may have failed to include the related model in your API, or incorrectly configured the 'lookup_field' attribute on this field.
[08/Mar/2016 16:05:45] "GET /api/rehabilitation-session/ HTTP/1.1" 500 165647

And in my browser this:

How can I serialize a ForeignKey field? I appreciate your help.

UPDATE 2

I wanted to try using the nested relationships as I have suggested in this post, given that it would also be good to have in the serialization of my model in this case rehabilitation session, the fields of the patient of that session, the doctor of that session and the therapist of that session and not their respective urls that take me to the data themselves, for usability purposes and I imagine reading for some third application, although I also imagine that by the url you can also access the values and keys of the json of each url right?

Well, at this moment, I have them like this:

I would like to be able to organize my data in the following way:

{
    "url": "http://localhost:8000/api/rehabilitation-session/9/",
    "id": 9,
    "patient": [
        {'id' : 1, 'full_name' : 'Andres Enfermizo',
         'diagnostic' : 'Diabetes', 'time_of_evolution' : '3 months'
        },      
    "medical": [
        {'id' : 1, 'full_name' : 'Doctor House',
         'specialty' : 'Epidemiologist', 'xperience' : '23 years'
        } ,
    ]
    "therapist": [
        {'id' : 1, 'full_name' : 'Christian',
         'specialty' : 'Legs', 'xperience' : '13 years'
        } ,
    ]
    "affected_segment": [
        {'id' : 1, 'affected_segment' : 'shoulder',
         'damage' : '30%', 'time_of_retrieve' : '10 months'
        } ,

    ],
} 

So according to the documentation, Nested Relationships or even < Could you help?

Custom relational fields

I have done this by following the Nested Relationships guide in this way , for now only for the case of showing in a nested way the patient's data in the serialization of the rehabilitation session:

Serializing the model PatientProfile :

class PatientProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = PatientProfile
        fields = ('url','id','user','full_name','time_of_evolution','diagnostic','marital_status','educational_level','partner_full_name','partner_relationship','partner_phone','partner_gender',
                  'care_provider',)

The PatientProfileViewset view that the serialized model will expose

class PatientProfileViewSet(viewsets.ModelViewSet):
    queryset = PatientProfile.objects.all()
    serializer_class = PatientProfileSerializer

Now serialize my model RehabilitationSession :

class RehabilitationSessionSerializer(serializers.ModelSerializer):
    patient = PatientProfileSerializer(many=True, read_only=True)
    class Meta:
        model = RehabilitationSession
        fields = ('url','id','patient',
                  #'affected_segment',
                  'date_session_begin','status','upper_extremity',

                  'date_session_end', 'period','games','game_levels',
                  'iterations','observations',)

The RehabilitationSessionViewSet view that the serialized model will expose

class RehabilitationSessionViewSet(viewsets.ModelViewSet):
    queryset = RehabilitationSession.objects.all()
    serializer_class = RehabilitationSessionSerializer

And these are my routers for each serialized model to access them by url in the browser. They are in my main urls.py:

from django.conf.urls import url, include #patterns
from django.contrib import admin

from .views import home, home_files

# REST Framework packages
from rest_framework import routers
from userprofiles.views import (UserViewSet, GroupViewSet, PatientProfileViewSet,)

from medical_encounter_information.views import (RehabilitationSessionViewSet,)


router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'groups', GroupViewSet)
router.register(r'rehabilitation-session', RehabilitationSessionViewSet)
router.register(r'patientprofile', PatientProfileViewSet)

urlpatterns = [
    url(r'^admin/', admin.site.urls),

    ...
    url(r'^api/', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),

]

I get this message when I enter the url link

Any guidance will be highly appreciated. :)

    
asked by bgarcial 08.03.2016 в 17:25
source

3 answers

1

Finally, in the case of obtaining in a nested way the data in my answer in the JSON document (s) that my api generates with django-rest-framework , I have opted for the following solution:

I return from the beginning to give a description of what has been done, so I will start by showing which are my originally defined models, how serialized, how the views or endpoints are generated and finally how the routes are established ( router's ) to expose them type URLConf

These are my defined models

1. PatientProfile model, defined userprofiles/models.py

class PatientProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    full_name = models.CharField(max_length=64)
    time_of_evolution = models.CharField(max_length=64, blank=False)
    diagnostic = models.TextField(blank=True)

    class Meta:
        verbose_name='Usuarios con perfil de pacientes'

    def __str__(self):
        return '%s %s' % (self.user.first_name, self.user.last_name)

2. MedicalProfile model defined in userprofiles/models.py

class MedicalProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    full_name = models.CharField(max_length=64)
    specialty = models.CharField(max_length=64)

    class Meta:
        verbose_name='Usuarios con perfil de médicos'

    def __str__(self):
        return '%s %s' % (self.user.first_name, self.user.last_name)

3. TherapistProfile defined in userprofiles/models.py

class TherapistProfile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    full_name = models.CharField(max_length=64)
    specialty = models.CharField(max_length=64)

    class Meta:
        verbose_name='Usuarios con perfil de terapeutas'

    def __str__(self):
        return '%s %s' % (self.user.first_name, self.user.last_name)

Serializing

The serialization of these models I have it in this way in userprofiles/serializers.py

from .models import PatientProfile, MedicalProfile, TherapistProfile
from rest_framework import serializers

class PatientProfileSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = PatientProfile
            fields = ('url','id','user','full_name','time_of_evolution','diagnostic','marital_status','educational_level','partner_full_name','partner_relationship','partner_phone','partner_gender',
                      'care_provider',)

class MedicalProfileSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = MedicalProfile
            fields = ('url','id','user','full_name','specialty')

class TherapistProfileSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = TherapistProfile
            fields = ('url','id','user','full_name','specialty')

The views that expose as endpoints the previous serialized models are given in userprofiles/views.py in this way:

from .models import PatientProfile, MedicalProfile, TherapistProfile
from rest_framework import viewsets
from .serializers import (PatientProfileSerializer, MedicalProfileSerializer, TherapistProfileSerializer,)

class PatientProfileViewSet(viewsets.ModelViewSet):
    queryset = PatientProfile.objects.all()
    serializer_class = PatientProfileSerializer

class MedicalProfileViewSet(viewsets.ModelViewSet):
    queryset = MedicalProfile.objects.all()
    serializer_class = MedicalProfileSerializer

class TherapistProfileViewSet(viewsets.ModelViewSet):
    queryset = TherapistProfile.objects.all()
    serializer_class = TherapistProfileSerializer

I present the model RehabilitationSessions located in medical_encounter_information / models.py which has three foreign keys to the previous user profiles PatientProfile , MedicalProfile and TherapistProfile

from django.db import models
from smart_selects.db_fields import ChainedManyToManyField
from django.utils import timezone

from multiselectfield import MultiSelectField

class RehabilitationSession(models.Model):

    EXTREMITY_CHOICES = (
        ('Extremidad superior derecha', 'Superior Derecha'),
        ('Extremidad superior izquierda', 'Superior Izquierda'),
    )

    # Primer campo con ForeignKey relationship 
    patient = models.ForeignKey('userprofiles.PatientProfile',      null=True, blank=True,                                verbose_name='Paciente', related_name='patientprofile')

    # Segundo campo con ForeignKey relationship 
    medical = models.ForeignKey('userprofiles.MedicalProfile', null=True, blank=True,
                                verbose_name='Médico tratante', related_name='medicalprofile')

    # Tercer campo con ForeignKey relationship
    therapist = models.ForeignKey('userprofiles.TherapistProfile', null=True, blank=True,
                               verbose_name='Terapeuta')


    date_session_begin = models.DateTimeField(default=timezone.now(), verbose_name = 'Fecha de inicio')

    upper_extremity = MultiSelectField(
        max_length=255,
        choices=EXTREMITY_CHOICES,
        blank=False,
        verbose_name='Extremidad Superior'
    )

    affected_segment = models.ManyToManyField(AffectedSegment,verbose_name='Segmento afectado', related_name='affectedsegment')
    movement = ChainedManyToManyField(
        Movement, #Modelo encadenado
        chained_field = 'affected_segment',
        chained_model_field = 'corporal_segment_associated',
        verbose_name='Movimiento'
    )

    metrics = models.ManyToManyField(Metric, blank=True, verbose_name='Métrica')
    date_session_end = models.DateTimeField(default=timezone.now(), verbose_name = 'Fecha de finalización')
    period = models.CharField(max_length=25,blank=True, verbose_name='Tiempo de duración de la sesión')

    observations = models.TextField(blank=False, verbose_name='Observaciones')

    class Meta:
        verbose_name = 'Sesiones de Rehabilitación'

    def __str__(self):
        return "%s" % self.patient

The serialization of the fields of the class RehabilitationSession is given by the class RehabilitationSessionSerializer located in medical_encounter_information/serializers.py

This is where I had my doubts about how to perform the serialization and for which I was consulting a little about how relations are handled in Django REST Framework .

I found in the documentation how to work the serialization in a nested way without the need to resort to extra classes such as Serializer Relationships .

The key is to add a field called depth which recursively allows n levels of depth (not really until a certain number of levels) to be able to serialize the JSON subdocuments that we have, in this case the ones that were each ForeignKey of each user profile needed in a rehabilitation session ( PatientProfile , MedicalProfile , TherapistProfile )

Here it says the following:

  

The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.

     

If you want to customize the way the serialization is done, you'll need to define the field yourself.

Then my class RehabilitationSessionSerializer stays like this:

from .models import (RehabilitationSession,) 
from rest_framework import serializers
class RehabilitationSessionSerializer(serializers.ModelSerializer):

    class Meta:
        model = RehabilitationSession
        fields = ('url','id','patient','medical','therapist','affected_segment','movement','metrics','date_session_begin',
                  'status','slug', 'date_session_end','period','games','game_levels',
                  'iterations','observations',)
        #read_only_fields = ('patient','medical','therapist',)
        depth=1

The ViewSet view that this serialization exposes is given in this way in medical_encounter_information/views.py

from .models import RehabilitationSession
from rest_framework import viewsets
from .serializers import (RehabilitationSessionSerializer,)

class RehabilitationSessionViewSet(viewsets.ModelViewSet):
    queryset = RehabilitationSession.objects.all()
    serializer_class = RehabilitationSessionSerializer

My routes / routers in urls.py for access to the models exposed via rest framework are:

from rest_framework import routers
from userprofiles.views import PatientProfileViewSet,                 MedicalProfileViewSet, TherapistProfileViewSet

from medical_encounter_information.views import (RehabilitationSessionViewSet, AffectedSegmentViewSet, MovementViewSet,
                                                 MetricViewSet,)

# Routers provide an easy way of automatically determining the URL conf.
# Because we're using viewsets instead of views, we can automatically
# generate the URL conf for our API, by simply registering the viewsets with a router class.
router = routers.DefaultRouter()
router.register(r'rehabilitationsession', RehabilitationSessionViewSet)
router.register(r'patientprofile', PatientProfileViewSet)
router.register(r'medicalprofile', MedicalProfileViewSet)
router.register(r'therapistprofile', TherapistProfileViewSet)
router.register(r'affectedsegment', AffectedSegmentViewSet)
router.register(r'movement', MovementViewSet)
router.register(r'metrics', MetricViewSet)



urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^chaining/', include('smart_selects.urls')),
    url(r'^$', home, name='home'),

    url(r'^', include('userprofiles.urls')),
    #Call the userprofiles/urls.py

    url(r'^', include('medical_encounter_information.urls' )),
    #Call the medical_encounter_information/urls.py

    #REST Frameworks url's
    # Wire up our API using automatic URL routing.
    # Additionally, we include login URLs for the browsable API.

    url(r'^api/', include(router.urls)),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
    # Including default login and logout views for use with the browsable API.
    # That's optional, but useful if your API requires authentication and you want to use the browsable API.
    # Note that the URL path can be whatever you want, but you must include 'rest_framework.urls' with the 'rest_framework' namespace.
    # You may leave out the namespace in Django 1.9+, and REST framework will set it for you.
]

It is then that when I consult this API in my browser, the RehabilitationSession model that has the three foreign keys to the models PatientProfile , MedicalProfile and TherapistProfile ; When it is serialized, it is displayed in the following way with the documents of the respective user profiles mentioned, nested:

This is how I could have a serialization of several models in the same view, so to speak.

Of course there are several classes like Serialization relationships that allow handle the relationships between models in django rest framework according to what we need, if it is a url, if it is a string, if it is a slug type field or if it is a PrimaryKey type field or if it is nested as what was needed

I also found this example of nested serialization or recursive for fields, but this time it is the same model, interesting and very necessary, for a very common example on the web that is the one of comments and answers to those same comments.

I also want to thank you very much for the ideas and guidelines, they were useful for finding my solution: D

    
answered by 11.03.2016 / 23:43
source
1

Use Nested Relations

For example:

class TrackSerializer(serializers.ModelSerializer):
    class Meta:
        model = Track
        fields = ('order', 'title', 'duration')

class AlbumSerializer(serializers.ModelSerializer):
    tracks = TrackSerializer(many=True, read_only=True)

    class Meta:
        model = Album
        fields = ('album_name', 'artist', 'tracks')

where tracks of the Album models is a foreign key to the Track model.

    
answered by 08.03.2016 в 17:55
1

Beware of nested relationships, they are a kill performance for your API, I talk about it in this post: link

On the other hand, I would use the class MethodSerializers to create the serializers as personalized as possible.

Greetings.

    
answered by 01.09.2016 в 15:52