Django - queryset vs model in Generic View - python

I'm pretty new at Django and wondering what is the difference between defining model vs queryset in a generic view like ListView. Here's my code example in my urls.py file for the project:
urlpatterns = patterns('',
url(r'^$', ListView.as_view(
model=Person,
context_object_name='people',
template_name='index.html',
)),
)
I've also used the same this:
urlpatterns = patterns('',
url(r'^$', ListView.as_view(
queryset=Person.objects.all,
context_object_name='people',
template_name='index.html',
)),
)
And received the same result on my view. I'm assuming there are different things you can do with a queryset?

Using model=Person or queryset=Person.objects.all give the same result.
Let's look at the code. A ListView has the following method:
def get_queryset(self):
"""
Get the list of items for this view. This must be an interable, and may
be a queryset (in which qs-specific behavior will be enabled).
"""
if self.queryset is not None:
queryset = self.queryset
if hasattr(queryset, '_clone'):
queryset = queryset._clone()
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured(u"'%s' must define 'queryset' or 'model'"
% self.__class__.__name__)
return queryset
As you can see, it first looks for self.queryset and, if that does not exist, for self.model. So there are two possibilities to specify a list: you can provide a queryset yourself or you can specify a model class (in which case Django will call the all() method of the default manager, which is objects).
I'm assuming there are different things you can do with a queryset?
Yes. If you specify a model, then you get all instances by default. But if you specify a queryset, you can also call other methods of a model manager, such as Person.objects.children() which could return only persons with age <= 12.

Related

DRF: How do you create multiple "detail" views for a model?

I have a django model that has multiple fields that need to be used as a key, and also has some detail views.
For example, my endpoints currently look like this, using detail=True to get the second set:
my.api/things/{id_1} (GET, POST, DELETE)
my.api/things/{id_1}/whatever (GET, POST)
That's all great, but I have to get to something that looks like this instead:
my.api/things/{id_1} (GET, POST, DELETE)
my.api/things/{id_1}/whatever (GET, POST)
my.api/things/other_id/{id_2} (GET, POST, DELETE)
my.api/things/other_id/{id_2}/whatever (GET, POST)
If it helps, the set of detail endpoints (ie. whatever) is identical, and there's no difference in functionality between the two. I just need to be able to access the database through either field.
I'm new to django so I'm sorry if this is a simple question. Any help would be appreciated!
You can simply inherit base class and make whatever you want.
For example,
class MultipleFieldLookupMixin:
"""
Apply this mixin to any view or viewset to get multiple field filtering
based on a `lookup_fields` attribute, instead of the default single field filtering.
"""
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
You can then simply apply this mixin to a view or viewset anytime you need to apply the custom behavior.
class RetrieveUserView(MultipleFieldLookupMixin, generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
lookup_fields = ['account', 'username']

list_filters are not working with redefined get_queryset in Admin list_view if that contains union

I needed to create Django Admin list_view page with redefined get_queryset() method that will do union by different models to the same page.
But after I updated it, list_filter stopped work.
As I find out if you use union() - Django doesn't allow to do any further filtering (only LIMIT, OFFSET, COUNT(*), ORDER BY SQL operations).
And for some reason application of these filters is implemented after call of get_queryset(). I thought that call super().get_queryset(request) in my ModelAdmin.get_queryset() will return already parsed data.
UPD: In this situation no filters working and queryset in actions always contain all model records, even if few selected just a few.
class Model1():
pass
class ModelA():
x = models.ForeignKey(Model1)
class ModelB():
x = models.ForeignKey(Model1)
class Model1Admin():
list_display = (...)
list_filters = (...) # These will not work
list_actions = (...) # These will not work
def get_queryset():
qs_a = Model1.prefetch_related('ModelA')
qs_b = Model1.prefetch_related('ModelB')
return qs_a.union(qs_b)
Questions:
Is there any simple way how to get queryset with already applied list_filters and after do my custom filtering and union?
Is this a bug in Django Framework by itself and this should be reported in their bug-tracker?

Remove specific actions from existing drf model viewset

I re-use an existing drf model viewset but there are some custom actions (assigned with #action label) that i don't need. How can I hide/remove it from django rest framework without modifying the origional model viewset?
for example
class MyViewSet(viewsets.ModelViewSet):
#action(["get",], detail=False)
def custom_a(self, request):
# some stuff
#action(["get",], detail=False)
def custom_b(self, request):
# some stuff
#action(["get",], detail=False)
def custom_c(self, request):
# some stuff
My router
router = routers.SimpleRouter()
router.register("dummies", views.MyViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Then I will have these endpoints
GET /dummies/
GET /dummies/{id}/
POST /dummies/
PUT /dummies/{id}/
PATCH /dummies/{id}/
DELETE /dummies/{id}/
GET /dummies/custom_a/
GET /dummies/custom_b/
GET /dummies/custom_c/
Now how can I just keep 5 first views and GET /dummies/custom_a/?
Thanks.
There are a few ways to do this, but the "cleanest" seems overriding.
Override & Ignore
Decorators are not inherited, so you can just re-declare the method on your derived class. This new method takes precedence over the base class method, so DRF no longer sees them as #actions.
class View1(viewsets.ModelViewSet):
#action(['get'], detail=False)
def act_up(self, request):
pass
class Meta:
model = Client
fields = "__all__"
class View2(View1):
# redefine and don't add the #action decorator
def act_up(self, request):
pass
class Meta:
model = View1.Meta.model
fields = View1.Meta.fields
Use a router and just removed the methods you don't want
Router URLs are calculated once and then cached. You could make this happen, then filter out the ones you don't want (by name)
router = SimpleRouter()
router.register("view1", View1, basename="v1")
router._urls = [
r for r in router.urls
if not any(r.name.endswith(bad) for bad in ['-act-up', '-other-rt']
]
Manually route to the actions
You can manually create the routes you need, in the same manner that the base SimpleRouter does. This is a lot more work, and most certainly not worth it
list_paths = View1.as_view({"get": "list"})
detail_paths = View1.as_view({"get": "retrieve", "patch": "partial_update"})
urlpatterns = [
path("view1/", list_paths, name="view1-list"),
path("view1/<int:pk>/", detail_paths, name="view1-detail")
]
What this does is bind a url + http method (e.g. get) to a specific DRF "action" (list/retrieve/destroy,etc). So GET view11/ calls your viewset with action=list, which the viewset then dispatches to the correct method internally.
Since you only map the things you want, there is no way for those bad "other" methods to be called.
It is a bit trickier, and confusing, and makes you responsible for the routing which, together, make it more work to maintain & understand. If there is no other way, or if you only want 1 or 2 methods from the ViewSet, then its an OK option.

Append additional values to queryset in Django generic views before handing to template

I have the following view:
class AppointmentListView(LoginRequiredMixin, ListView):
queryset = Appointment.objects.prefetch_related('client','patients')
I need to be able to add an extra variable to each returned Appointment object based on the following:
status_choices={
'STATUS_UPCOMING':'default',
'STATUS_ARRIVED':'primary',
'STATUS_IN_CONSULT': 'success',
'STATUS_WAITING_TO_PAY':'info',
'STATUS_PAYMENT_COMPLETE':'warning',
}
The values ('default', 'primary' etc) correspond to standard css classesin Bootcamp themes that I want to use according to the type of Appointment. For example, 'default' produces a gray button, 'warning' a red button etc.
I need to map each Appointment record to a certain css button based on the record's status ('upcoming' would display the 'default' class etc).
My initial idea was to loop over the query set and build a separate array/dictionary mapping the Appointment pk to a given css class such as
1:'success', 2:'warning', and then pass that in as a context variable.
But I was wondering if I could just add the value to each Appointment object directly (perhaps saving the queryset as a list?) That would be a much cleaner solution but am not sure how that should be approached.
Any ideas much appreciated
You should overload the get_queryset method of the ListView like so
def get_queryset(self, **kwargs):
queryset = super(AppointmentListView, self).get_queryset(**kwargs)
# Add new elements here
...
return queryset
I got this working by overriding get_queryset() and giving the objects (i.e. each row in the db) an extra on-the-fly key/value:
class AppointmentListView(LoginRequiredMixin,ListView):
#friendly template context
context_object_name = 'appointments'
template_name = 'appointments/appointment_list.html'
def get_queryset(self):
qs = Appointment.objects.prefetch_related('client','patients')
for r in qs:
if r.status == r.STATUS_UPCOMING: r.css_button_class = 'default'
if r.status == r.STATUS_ARRIVED: r.css_button_class = 'warning'
if r.status == r.STATUS_IN_CONSULT: r.css_button_class = 'success'
if r.status == r.STATUS_WAITING_TO_PAY: r.css_button_class = 'danger'
if r.status == r.STATUS_PAYMENT_COMPLETE: r.css_button_class = 'info'
return list(qs)
A couple of things:
I converted the queryset qs to a list to 'freeze' it. This prevents the queryset from being re-evaluated (e.g. slice) which, in turn, would cause the on-the-fly model changes to be lost as fresh data is pulled from DB.
I needed to assign a value to template_name explicitly. When overriding get_queryset the template name is not derived automagically. As a comparison, the code below whose queryset attribute is set, generates the template name automatically:
class AppointmentListView(LoginRequiredMixin, ListView):
queryset = Appointment.objects.prefetch_related('client', 'patients')
#template name FOO_list derived automatically
#appointments/views.py
...
#can use derived name (FOO_list)
{% for appointment in appointment_list %}
...

How to add GET parameters to django-tables2 LinkColumn

I am trying to set GET parameters on a LinkColumn based on Accessors with django-tables2.
Let's say:
urls.py
urlpatterns = [
...
url(r'^rqGET$', views.rqGET, name='rqGET'),
...
]
views.py
def rqGET(request):
#... do something with request.GET
tables.py
class MyTable(tables.Table):
id = LinkColumn('rqGet',text='Link') # do something with Accessors to make a GET string, maybe ?id=A('pk')
class Meta:
model = MyModel #(has field 'id')
I want to use reverse to get the correct url, then construct the GET parameter string. For example /rqGET?id=1 ('1' would be different in each row).
That's not really how Accessors work in django-tables2. Django-tables2 uses django's reverse to generate urls. If you want reverse to be able to generate the url, you need to define the parameters in your urls, which will be passed as arguments to your view function:
# urls.py
urlpatterns = [
...
url(r'^rqGET/(?P<id>\d+)$', views.rqGET, name='rqGET'),
...
]
# views.py
def rqGET(request, id):
# do something with request/id.
If you don't want to change the way your url is formatted, you can use a custom render_ function on your MyTable like this:
# tables.py
from django.core.urlresolvers import reverse
class MyTable(tables.Table):
id = LinkColumn('rqGet', text='Link') # do something with Accessors to make a GET string, maybe ?id=A('pk')
class Meta:
model = MyModel
def render_id(self, record):
url = reverse('rqGET')
return format_html('{}', url, record.id, 'Link')
This will render a link with format /rqGET/?id=<id>.

Categories