Search code examples
djangodjango-modelsdjango-viewsviewdjango-templates

Get the first three human readable elements in a queryset


I'm trying to render elements in a Django view. Every clinic object has many specialities, but for estetic reasons I only want the first three of them to be displayed in the template. I've tried:

def clinics_index(request):
    clinics = Clinic.objects.all()
    for clinic in clinics:
        speciality = clinic.get_speciality_display
    context = {
        'clinics' : clinics,
        'speciality' : speciality,
    }
    return render(request, 'guide/clinic/clinic_directory.html', context)

This now renders the human-readable name of the speciality field (which is a multiple choice field in the model). However, I can't use substraction to only get 3 elements like here:

speciality = clinic.get_speciality_display[:3]

As I get the following error:

TypeError at /guide/clinics/
'method' object is not subscriptable

How can I render it?

Edit:

This is the Clinic model:

class Clinic(models.Model):
    name = models.CharField(max_length=75, blank=True, null=True)
    speciality = MultiSelectField(choices=Speciality.choices, max_length=100, blank=True, null=True)
    city = models.CharField(max_length=20, choices=Cities.choices, blank=True, null=True)
    ward = models.CharField(max_length=20, choices=Wards.choices, blank=True, null=True)
    full_address = models.CharField(max_length=100, blank=True, null=True)
    maps_link = models.CharField(max_length=75, blank=True, null=True)
    train_access = models.CharField(max_length=50, blank=True, null=True)
    bus_access = models.CharField(max_length=50, blank=True, null=True)
    parking = models.CharField(_('Parking availability'), max_length=75, blank=True, null=True)
    phone_number = models.CharField(max_length=20, blank=True, null=True)
    english_support = models.BooleanField(default=False, blank=True, null=True)
    holiday_availability = models.BooleanField(_('Availability on weekends/holidays'), default=False, blank=True, null=True)
    slug = models.SlugField(blank=True, null=True)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('guide:clinic_detail', kwargs={"slug" : self.slug})

And the template snippet:

<tbody>
{% for clinic in clinics %}
<tr>
    <td>{{clinic.name}}</td>
    <td>{{clinic.city}}</td>
    <td>{{clinic.ward}}</td>
    <td>{{speciality}}</td>
    <td><a href="{{clinic.get_absolute_url}}">More...</a></td>
</tr>
{% endfor %}
</tbody>

EDIT:

This code is rendering the first 3 human readable elements as I wanted:

clinics = Clinic.objects.all()
for clinic in clinics:
   speciality = ','.join(clinic.get_speciality_display().split(',')[:3])

However, I am struggling to render it correctly with its correspondant instance. This code:

fff = [{'name': i.name, 'speciality': ','.join(i.speciality[:3])} for i in Clinic.objects.all()]

Is rendering the non-human readable names. How could connect both (and also display city and ward fields for each instance)?


Solution

  • I assume that in a loop you want to collect all the data. To do this, you need to save them to a list. But that's overkill, just pass clinics to a dictionary and iterate over all the values in the template. Also, for links, I used clinic.slug instead of clinic.get_absolute_url, since the model already returns the generated url through the get_absolute_url method.

    views.py

    def clinics_index(request):
        clinics = Clinic.objects.all()[:3]
    
        return render(request, 'guide/clinic/clinic_directory.html', {'context': clinics})
    

    templates

    {% for clinic in context %}
    <p>{{ clinic }}</p>
    <tr>
        <td>{{ clinic.name }}</td>
        <td>{{ clinic.city }}</td>
        <td>{{ clinic.ward }}</td>
        <td>{{ clinic.speciality }}</td>
        <td><a href="{{ clinic.slug }}">More...</a></td>
    </tr>
    {% endfor %}
    </tbody>
    

    Update 05.11.2022 To get the clinic.get_speciality_display() value, you need to call the method using parentheses. When I print out the value type, I get a string. Therefore, in order to take the first three elements, I turn the string into a list, select the desired number and again turn it into a string.

    So you can select the first three records:

    clinics = Clinic.objects.all()
    for clinic in clinics:
       speciality = ','.join(clinic.get_speciality_display().split(',')[:3])
    

    all code: views.py

    def clinics_index(request):
        #fff = [{'name': i.name, 'speciality': i.speciality[:3]} for i in Clinic.objects.all()]#if you need to display 'speciality' as a list
        fff = [{'name': i.name, 'speciality': ','.join(i.speciality[:3])} for i in Clinic.objects.all()]
    
        return render(request, 'guide/clinic/clinic_directory.html', {'context': fff})
    

    templates

    <tbody>
    {% for a in context %}
    <tr>
        <p><td>{{ a.name }}</td></p>
        <p><td>{{ a.speciality }}</td></p>
    
    </tr>
    {% endfor %}
    </tbody>
    

    If that's not what you need. Show what the data looks like and what you want to see.