I am working with the class-based view (CBV) ListView , which what it does is return a list of objects where I can do:
1. If I use ListView
it without changing its class or doing anything, it returns me a list of, for example, all the objects of a model without much effort, really, for which we only need to give it the model and the template to render.
2. I can use its method get_queryset()
to tell it to filter by a given attribute or to run a custom query to return specific objects according to the given queryset criteria.
I am using ListView
to retrieve the list of users from the User model which is a custom model that is in a created application called userprofile.
# settings.py
INSTALLED_APPS = [
...
'userprofile',
...
]
AUTH_USER_MODEL = 'userprofile.User'
In the User model in models.py I am adding some special attributes, among them a field called slug
, to be able to consult the data of a specific user.
A slug is part of a url that makes it more friendly for humans to read. It helps a lot with SEO.
# userprofile/models.py
from __future__ import unicode_literals
from django.contrib.auth.models import AbstractUser
from django.db import models
# Create your models here.
class User(AbstractUser):
is_medical = models.BooleanField(default=False)
is_physiotherapist = models.BooleanField(default=False)
is_patient=models.BooleanField(default=False)
slug = models.SlugField(max_length=100, blank=True)
#slug = models.CharField(max_length=100, blank=True)
photo = models.ImageField(upload_to='avatars')
class Meta:
db_table = 'auth_user'
class MedicalProfile(models.Model):
user=models.OneToOneField(User, on_delete=models.CASCADE)
active = models.BooleanField(default=True)
name = models.CharField(max_length=64)
class PatientProfile(models.Model):
user=models.OneToOneField(User, on_delete=models.CASCADE)
active = models.BooleanField(default=True)
name = models.CharField(max_length=64)
class PhysiotherapistProfile(models.Model):
user=models.OneToOneField(User, on_delete=models.CASCADE)
active = models.BooleanField(default=True)
name = models.CharField(max_length=64)
At the moment, my ListView to consult the list of users I have called UserListView and it is this:
# userprofile/views.py
from django.shortcuts import render
from django.views.generic import ListView
from .models import User
#from django.http import JsonResponse
class UserListView(ListView):
model = User
template_name = 'user_list.html'
My user_list.html template receives a object_list
from the view and cycles through the model objects. If said object_list
arrives empty, it goes to the empty tag and notifies that there are no objects (users) yet to show
# userprofile/templates/user_list.html
{% block content %}
<h1>Users</h1>
<div>
{% for users in object_list %}
<div>
{{ users.username }}
{{ users.password }}
{{ users.first_name }}
{{ users.last_name }}
{{ users.photo }}
{{ users.email }}
{{ users.slug }}
<br />
</div>
{% empty %}
<div>No hay usuarios todavia</div>
{% endfor %}
</div>
{% endblock %}
In my userprofiles/urls.py file I have my url that calls the view UserListView
like this:
from django.conf.urls import url, patterns
from .views import UserListView
urlpatterns = [
url(r'^users/$', UserListView.as_view(), name='users_list'),
]
It is then, as when I access the url http://localhost:8000/users/
I get a list of the instances of the User model, that is, my users
So far everything works fine and I wanted to include this process, to show what I want to do now and that is where my problem is:
I want to change the behavior of the cbv queryset, [ListView][1]
that is, override the method get_queryset()
or the second option I was talking about at the beginning.
What I basically want is that this view of UserListView, which already returns my list of users, if I pass the name of a particular user in the url, it only brings me the data of one user. For example:
This example is not working, it was only adapted for illustration purposes.
This is when the slug field that I added to the User model in userprofile/models.py comes into play.
class User(AbstractUser):
is_medical = models.BooleanField(default=False)
is_physiotherapist = models.BooleanField(default=False)
is_patient=models.BooleanField(default=False)
slug = models.SlugField(max_length=100, blank=True)
#slug = models.CharField(max_length=100, blank=True)
photo = models.ImageField(upload_to='avatars')
So if what I want is to get only the data of a particular user or instance, I must change the get_queryset() method so that it behaves according to my needs.
I override it in the UserListView created in userprofile/wiews.py
- First I add a new url that receives the name of that user. This url will be called users and it receives a regular expression where I tell it that the user that is going to receive (the word
username
) will be part of the regular expression:
And I also tell him that this regular expression allows alphanumeric characters (with the w) both lowercase and uppercase and that it allows underscores and that they can be many times. This regular expression on this url is called username
and points to the viewUserListView
# userprofile/urls.py
from django.conf.urls import url, patterns
from .views import LogoutView, UserListView
urlpatterns = [
url(r'^users/$', UserListView.as_view(), name='users_list'),
url(r'^users/(?P<username>[\w\-]+)/$', UserListView.as_view(), name='users_list'),
]
is the name of the user that we will send in the url
- Overriding get_queryset()
This is how in the UserListView view I override the methodget_queryset()
In Class Based Views, all the url parameters and extra parameters that I send to a view all go to a variable called kwargs
def get_queryset(self):
if self.kwargs
So, I ask that if within those kwargs
I have the username that is in the url, if the username exists, then that it return the data of that username only and if not, that it act as the original query_set of the view based on the ListView class, like before I started overwriting it:
Note that in the queryset, I am indicating that it should search for the slug of the user or the User model, which is username
(Although here I have doubts as to whether this is well thought out given the error that I will show a little later)
In the raised queryset, I want to say: Return all the data where the user's slug is the one that came from the url. That is, theusername
And if it does not bring the slug with some name of an existing user, but only http://localhost:8000/users/ then it returns all the users of the User model, which is how I have it at first and it works for me, given which is the normal behavior of cbv's get_queryset() method ListView
. For that I use the super, to call the parent of the UserListView which is the ListView and specifically the original get_queryset() function is called.
def get_queryset(self):
if self.kwargs.get('username'):
#Devolver datos de usuario definimos un queryset para ello
queryset = self.model.objects.filter(username__slug=self.kwargs['username'])
else:
#Actue igual que siempre el queryset
queryset = super(UserListView, self).get_queryset()
return queryset
That is when I want to query the data of a particular user and I get the following error message:
That's how I get to my concern, and to be precise, I'm not too sure how I'm formulating the specific queryset, since I'm telling it to apply it to the User model and filter by the username attribute of that model (that's for that attribute or so is the name of the slug in the regular expression of the url that I propose for this functionality), only that I have my doubts in this part of defining the queryset.
queryset = self.model.objects.filter(username__slug=self.kwargs['username'])
Maybe someone can guide me a little on this part? Thank you very much.
What I would do is the following, if that field is new, I assume that there would be users whose field
slug
is empty or null. Then you could create asignal
when creating the user so that its field is savedslug
based on itsusername
usingslugify
:If you decide to do the above by overriding the
save()
model method then it won't work for cases where you create a user from the admin, that's why it's better to use asignal
.You
urls.py
would be something like this:And you
get_queryset()
:Note:
I haven't had time to test it in my project but I'm pretty sure it should work, you tell me.